Merge pull request #16 from elder-plinius/claude/fix-openrouter-translation-g0rDz

This commit is contained in:
pliny
2026-03-20 19:44:33 -07:00
committed by GitHub
5 changed files with 100 additions and 15 deletions
+29
View File
@@ -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
+4 -2
View File
@@ -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);
+7 -2
View File
@@ -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;
+20 -5
View File
@@ -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;
+40 -6
View File
@@ -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 = '';