🔒️ Security note: The API keys are stored in your browser's storage and synced across devices if the browser is configured to do so. 31 | They are sent by the extension to the LLM providers only.
32 | 33 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | async function saveOption(e, name, value) { 2 | e.preventDefault(); 3 | const toSave = {}; 4 | toSave[name] = value; 5 | try { 6 | await browser.storage.sync.set(toSave); 7 | } catch (e) { 8 | alert("Error saving option: " + JSON.stringify(e)); 9 | } 10 | } 11 | 12 | async function loadOptions() { 13 | const options = await browser.storage.sync.get([ 14 | PROVIDER_CONF, 15 | OPENAI_API_KEY_CONF, 16 | OPENAI_MODEL_CONF, 17 | ANTHROPIC_API_KEY_CONF, 18 | ANTHROPIC_MODEL_CONF, 19 | OLLAMA_MODEL_CONF, 20 | OLLAMA_URL_CONF, 21 | ]); 22 | 23 | const provider = options[PROVIDER_CONF] || DEFAULT_PROVIDER; 24 | if (provider === OPENAI_PROVIDER) { 25 | document.querySelector("#provider-openai").checked = true; 26 | } else if (provider === ANTHROPIC_PROVIDER) { 27 | document.querySelector("#provider-anthropic").checked = true; 28 | } else if (provider === OLLAMA_PROVIDER) { 29 | document.querySelector("#provider-ollama").checked = true; 30 | } 31 | 32 | const openaiApiKey = options[OPENAI_API_KEY_CONF]; 33 | if (openaiApiKey) { 34 | document.querySelector("#openai-api-key").value = openaiApiKey; 35 | } 36 | 37 | const openaiModel = options[OPENAI_MODEL_CONF] || DEFAULT_OPENAI_MODEL; 38 | document.querySelector("#openai-model").value = openaiModel; 39 | 40 | const anthropicApiKey = options[ANTHROPIC_API_KEY_CONF]; 41 | if (anthropicApiKey) { 42 | document.querySelector("#anthropic-api-key").value = anthropicApiKey; 43 | } 44 | 45 | const anthropicModel = options[ANTHROPIC_MODEL_CONF] || DEFAULT_ANTHROPIC_MODEL; 46 | document.querySelector("#anthropic-model").value = anthropicModel; 47 | 48 | const ollamaUrl = options[OLLAMA_URL_CONF]; 49 | if (ollamaUrl) { 50 | document.querySelector("#ollama-url").value = ollamaUrl; 51 | } 52 | const ollamaModel = options[OLLAMA_MODEL_CONF]; 53 | if (ollamaModel) { 54 | document.querySelector("#ollama-model").value = ollamaModel; 55 | } 56 | } 57 | 58 | document.addEventListener('DOMContentLoaded', loadOptions); 59 | 60 | document.addEventListener('DOMContentLoaded', function () { 61 | function createChangeListener(confName) { 62 | return async (event) => { 63 | await saveOption(event, confName, event.target.value); 64 | } 65 | } 66 | 67 | // OpenAI 68 | document.getElementById('provider-openai') 69 | .addEventListener('change', createChangeListener(PROVIDER_CONF)); 70 | document.getElementById('openai-api-key') 71 | .addEventListener('blur', createChangeListener(OPENAI_API_KEY_CONF)); 72 | document.getElementById('openai-model') 73 | .addEventListener('change', createChangeListener(OPENAI_MODEL_CONF)); 74 | 75 | 76 | // Anthropic 77 | document.getElementById('provider-anthropic') 78 | .addEventListener('change', createChangeListener(PROVIDER_CONF)); 79 | document.getElementById('anthropic-api-key') 80 | .addEventListener('blur', createChangeListener(ANTHROPIC_API_KEY_CONF)); 81 | document.getElementById('anthropic-model') 82 | .addEventListener('change', createChangeListener(ANTHROPIC_MODEL_CONF)); 83 | 84 | 85 | // Ollama 86 | document.getElementById('provider-ollama') 87 | .addEventListener('change', createChangeListener(PROVIDER_CONF)); 88 | document.getElementById('ollama-url') 89 | .addEventListener('blur', createChangeListener(OLLAMA_URL_CONF)); 90 | document.getElementById('ollama-model') 91 | .addEventListener('blur', createChangeListener(OLLAMA_MODEL_CONF)); 92 | }); 93 | -------------------------------------------------------------------------------- /src/options_const.js: -------------------------------------------------------------------------------- 1 | const PROVIDER_CONF = 'options.provider'; 2 | const OPENAI_API_KEY_CONF = 'options.openai.api_key'; 3 | const OPENAI_MODEL_CONF = 'options.openai.model'; 4 | const ANTHROPIC_API_KEY_CONF = 'options.anthropic.api_key'; 5 | const ANTHROPIC_MODEL_CONF = 'options.anthropic.model'; 6 | const OLLAMA_URL_CONF = 'options.ollama.url'; 7 | const OLLAMA_MODEL_CONF = 'options.ollama.model'; 8 | 9 | const OPENAI_PROVIDER = 'openai'; 10 | const ANTHROPIC_PROVIDER = 'anthropic'; 11 | const OLLAMA_PROVIDER = 'ollama'; 12 | const DEFAULT_PROVIDER = OPENAI_PROVIDER; 13 | 14 | const DEFAULT_OPENAI_MODEL = 'gpt-4o-mini'; 15 | const DEFAULT_ANTHROPIC_MODEL = 'claude-3-5-haiku-20241022'; 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyPlugin = require("copy-webpack-plugin"); 2 | const path = require('path'); 3 | 4 | module.exports = (env, argv) => { 5 | return { 6 | entry: { 7 | }, 8 | output: { 9 | // filename: '[name].bundle.js', 10 | path: path.resolve(__dirname, 'dist-firefox') 11 | }, 12 | devtool: argv.mode === 'production' ? "source-map" : "inline-cheap-source-map", 13 | optimization: { 14 | // This ensures each entry point gets its own file 15 | splitChunks: { 16 | chunks: 'async' 17 | }, 18 | minimize: false 19 | }, 20 | plugins: [ 21 | new CopyPlugin({ 22 | patterns: [ 23 | { from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js', to: 'browser-polyfill.js' }, 24 | { from: 'src/background.js', to: 'background.js' }, 25 | { from: 'src/content.js', to: 'content.js' }, 26 | { from: 'src/options.html', to: 'options.html' }, 27 | { from: 'src/options.js', to: 'options.js' }, 28 | { from: 'src/options_const.js', to: 'options_const.js' }, 29 | { from: 'manifest-firefox.json', to: 'manifest.json' }, 30 | { from: 'icons/*.png', to: '[name][ext]' } 31 | ] 32 | }) 33 | ] 34 | }; 35 | }; 36 | --------------------------------------------------------------------------------