diff --git a/README.md b/README.md index 610d4e9..7300f94 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,25 @@ A powerful web-based text transformation and steganography tool that can encode/ - **Fallback Methods**: Tries all available decoders if primary fails - **Real-time Processing**: Instant decoding as you type +### 🌐 **AI Translation** (via OpenRouter) +- **20+ Languages**: Major world languages (Spanish, French, Chinese, Japanese, Korean, etc.) +- **Dead & Exotic Languages**: Latin, Sanskrit, Ancient Greek, Sumerian, Akkadian, Old English, and more +- **Custom Languages**: Add any language on-the-fly +- **Multiple Models**: Gemma 3, Gemini 2.5 Flash, TranslateGemma (purpose-built translation models) +- **TranslateGemma Prompt Format**: Uses Google's optimized prompt template for high-quality translation +- **Auto-Fallback**: If a model is unavailable, automatically falls back to Gemma 3 27B + +### πŸͺ„ **PromptCraft** (AI Prompt Mutation) +- **9 Mutation Strategies**: Rephrase, Obfuscate, Role-Play Wrap, Multi-Language, Expand, Compress, Metaphor, Fragment, and Custom +- **48+ Models**: Frontier (Claude, GPT, Gemini, Grok), Reasoning (o3, o4, DeepSeek R1), Fast (Haiku, Mini), Code-specialized, Open Source (Llama, Qwen), and Search/Research models +- **Parallel Variants**: Generate 1-10 variants simultaneously with diverse temperature settings +- **Copy & Iterate**: Copy any variant or feed it back as input for iterative refinement + ### πŸ› οΈ **Available Tools** - **Universal Decoder**: Auto-detect and decode any supported format - **Text Transforms**: 79+ encoding, cipher, and transformation options +- **AI Translation**: Translate to 20+ languages via OpenRouter (built into Transforms tab) +- **PromptCraft**: AI-powered prompt mutation and crafting (dedicated tab) - **Steganography**: Emoji and invisible text steganography - **Tokenade Generator**: High-density token payload builder - **Mutation Lab (Fuzzer)**: Generate diverse text mutations @@ -102,6 +118,16 @@ A powerful web-based text transformation and steganography tool that can encode/ - **Responsive Design**: Works on all device sizes - **Accessibility**: Screen reader friendly with proper ARIA labels +### πŸ”‘ **OpenRouter API Key Setup** +Translation and PromptCraft features require an [OpenRouter](https://openrouter.ai/) API key: +1. Create an account at [openrouter.ai](https://openrouter.ai/) +2. Generate an API key (starts with `sk-or-...`) +3. In P4RS3LT0NGV3, click the **sliders icon** (top-right) to open **Advanced Settings** +4. Paste your key and click **Save Key** +5. Your key is stored locally in your browser only β€” never sent anywhere except OpenRouter + +> **Tip:** Some models (like Gemma 3) are free on OpenRouter. Frontier models (Claude, GPT, Gemini Pro) require credits. + ## πŸš€ **Getting Started** ### **Quick Start (Built Version)** @@ -171,6 +197,9 @@ npm run test:steg # Test steganography options - βœ… **Reverse Functions**: Added missing reverse functions for many transforms ### **New Features** +- πŸ†• **AI Translation**: Translate to 20+ languages (including dead/exotic) via OpenRouter using TranslateGemma prompt format +- πŸ†• **PromptCraft Tool**: AI-powered prompt mutation with 9 strategies and 48+ models +- πŸ†• **OpenRouter Integration**: Unified API key management for all AI-powered features - πŸ†• **79+ Transformations**: Added fantasy, ancient, and technical scripts - πŸ†• **More Encodings/Ciphers**: Base58, Base62, VigenΓ¨re, Rail Fence, Roman Numerals - πŸ†• **Category Organization**: Better organized transform categories diff --git a/js/app.js b/js/app.js index 701ef8c..324fc10 100644 --- a/js/app.js +++ b/js/app.js @@ -257,8 +257,10 @@ window.app = new Vue({ }, saveApiKey() { - if (this.openrouterApiKey) { - localStorage.setItem('openrouter-api-key', this.openrouterApiKey); + var trimmed = (this.openrouterApiKey || '').trim(); + if (trimmed) { + this.openrouterApiKey = trimmed; + localStorage.setItem('openrouter-api-key', trimmed); this.apiKeySaved = true; this.showNotification('API key saved', 'success'); setTimeout(() => { this.apiKeySaved = false; }, 2000); diff --git a/js/tools/DecodeTool.js b/js/tools/DecodeTool.js index 7a92556..f03aa03 100644 --- a/js/tools/DecodeTool.js +++ b/js/tools/DecodeTool.js @@ -188,9 +188,14 @@ class DecodeTool extends Tool { } }, decoderTranslateToEnglish: async function() { - var apiKey = localStorage.getItem('openrouter-api-key') || + var apiKey = (localStorage.getItem('openrouter-api-key') || localStorage.getItem('plinyos-api-key') || - localStorage.getItem('openrouter_api_key') || ''; + localStorage.getItem('openrouter_api_key') || '').trim(); + // Fallback: check Vue data property if localStorage is empty + if (!apiKey && this.openrouterApiKey) { + apiKey = this.openrouterApiKey.trim(); + localStorage.setItem('openrouter-api-key', apiKey); + } if (!apiKey) { this.decoderTranslateError = 'No API key. Set your OpenRouter key in Advanced Settings.'; return; diff --git a/js/tools/PromptCraftTool.js b/js/tools/PromptCraftTool.js index 2ad91b8..cbd0cbc 100644 --- a/js/tools/PromptCraftTool.js +++ b/js/tools/PromptCraftTool.js @@ -93,9 +93,15 @@ class PromptCraftTool extends Tool { getVueMethods() { return { pcGetApiKey: function() { - return localStorage.getItem('plinyos-api-key') || - localStorage.getItem('openrouter-api-key') || + var key = localStorage.getItem('openrouter-api-key') || + localStorage.getItem('plinyos-api-key') || localStorage.getItem('openrouter_api_key') || ''; + // Fallback: if nothing in localStorage, check the Vue data property + if (!key && this.openrouterApiKey) { + key = this.openrouterApiKey; + localStorage.setItem('openrouter-api-key', key.trim()); + } + return key.trim(); }, pcGetSystemPrompt: function() { const strategyPrompts = { @@ -143,7 +149,7 @@ class PromptCraftTool extends Tool { headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json', - 'HTTP-Referer': window.location.origin, + 'HTTP-Referer': window.location.href || 'https://p4rs3lt0ngv3.app', 'X-Title': 'P4RS3LT0NGV3 PromptCraft' }, body: JSON.stringify({ @@ -155,7 +161,12 @@ class PromptCraftTool extends Tool { temperature: 0.9 + (i * 0.05), max_tokens: 2048 }) - }).then(r => r.json()) + }).then(function(r) { + if (r.status === 401) throw new Error('Invalid API key. Check your OpenRouter key in Advanced Settings.'); + if (r.status === 402) throw new Error('Insufficient credits on your OpenRouter account.'); + if (r.status === 403) throw new Error('Access denied. Your key may lack permissions for this model.'); + return r.json(); + }) ); } @@ -165,7 +176,11 @@ class PromptCraftTool extends Tool { 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) { - this.pcError = result.value.error.message || 'API error'; + var errMsg = (typeof result.value.error === 'string') ? result.value.error : + (result.value.error.message || 'API error'); + this.pcError = errMsg; + } else if (result.status === 'rejected') { + this.pcError = result.reason.message || 'Request failed'; } } this.pcOutputs = outputs; diff --git a/js/tools/TranslateTool.js b/js/tools/TranslateTool.js index c340f84..5729852 100644 --- a/js/tools/TranslateTool.js +++ b/js/tools/TranslateTool.js @@ -84,9 +84,17 @@ class TranslateTool extends Tool { var self = this; return { translateGetApiKey: function() { - return localStorage.getItem('plinyos-api-key') || - localStorage.getItem('openrouter-api-key') || + var key = localStorage.getItem('openrouter-api-key') || + localStorage.getItem('plinyos-api-key') || localStorage.getItem('openrouter_api_key') || ''; + // Fallback: if nothing in localStorage, check the Vue data property + // (covers case where user typed key but forgot to click Save) + if (!key && this.openrouterApiKey) { + key = this.openrouterApiKey; + // Auto-save it so future calls work + localStorage.setItem('openrouter-api-key', key.trim()); + } + return key.trim(); }, translateGetLangCode: function(langName) { return self.langCodeMap[langName] || langName.toLowerCase().slice(0, 3); @@ -144,7 +152,7 @@ class TranslateTool extends Tool { headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json', - 'HTTP-Referer': window.location.origin, + 'HTTP-Referer': window.location.href || 'https://p4rs3lt0ngv3.app', 'X-Title': 'P4RS3LT0NGV3 TranslateGemma' }, body: JSON.stringify({ @@ -154,7 +162,29 @@ class TranslateTool extends Tool { max_tokens: 4096 }) }); - var data = await resp.json(); + + // Handle HTTP-level errors before parsing JSON + if (!resp.ok && resp.status === 401) { + this.translateError = 'Invalid API key. Check your OpenRouter key in Advanced Settings.'; + return; + } + if (!resp.ok && resp.status === 402) { + this.translateError = 'Insufficient credits on your OpenRouter account. Add credits at openrouter.ai.'; + return; + } + if (!resp.ok && resp.status === 403) { + this.translateError = 'Access denied. Your OpenRouter key may lack permissions for this model.'; + return; + } + + var data; + try { + data = await resp.json(); + } catch (parseErr) { + this.translateError = 'Unexpected response from OpenRouter (HTTP ' + resp.status + ')'; + return; + } + if (data.error) { // If TranslateGemma model not found, fall back to Gemma 3 27B if (isTranslateGemma && (data.error.code === 404 || data.error.code === 400 || @@ -168,15 +198,19 @@ class TranslateTool extends Tool { await this.translateTo(langName); return; } - this.translateError = data.error.message || 'API error'; + var errMsg = (typeof data.error === 'string') ? data.error : + (data.error.message || 'API error (code ' + (data.error.code || resp.status) + ')'); + this.translateError = errMsg; } 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); + } else { + this.translateError = 'No translation returned. Try a different model.'; } } catch (e) { - this.translateError = 'Translation failed: ' + e.message; + this.translateError = 'Translation failed: ' + (e.message || 'Network error. Check your connection.'); } finally { this.translateLoading = false; this.translateActiveLang = '';