Add files via upload

This commit is contained in:
pliny
2026-03-20 17:02:44 -07:00
committed by GitHub
parent 80f12b2321
commit b56e419bca
5 changed files with 1369 additions and 50 deletions
+235
View File
@@ -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;
}
+231
View File
@@ -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;
}