mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-05-29 19:29:32 +02:00
Merge pull request #16 from elder-plinius/claude/fix-openrouter-translation-g0rDz
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user