Skip to content

About

A browser extension to process, translate and refine text in any input field with LLMs, directly where you type

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ€– LLM Text Assistant - Browser Extension

A browser extension to process, translate and refine text in any input field with LLMs, directly where you type

Chrome Firefox Thunderbird Buy Me a Coffee Ko-fi

✨ Key Features

πŸ“Œ Core Features

  • Four built-in actions: 🌐 Translate, ✍️ Expand, πŸ“‹ Summarize, βœ… Grammar & Spelling, available via context menu and a floating πŸ€– icon next to any input field
  • Custom actions: Define your own actions with emoji, title and system prompt
  • Free Prompt (Chat mode): Open a chat window at the input field to give iterative instructions with full context β€” with ready-made preset chips (Shorter / More formal / As email / Bullet list) and an optional page context (title, URL, surrounding paragraphs)
  • Streaming results (live typing): Text appears token by token as the model generates it, no waiting for the full response
  • Works everywhere: <input>, <textarea> and contenteditable elements (including rich-text editors)
  • Review before replacing (optional): Show an oldβ†’new diff and only write the result after you confirm it

⌨️ Shortcuts & UX

  • Keyboard shortcuts: Assign shortcuts to built-in actions, custom actions and the free prompt β€” triggered only while a text field is focused, and they always win over page shortcuts (a document_start guard registers the first capture listener on window, so a matched combination never reaches the page β€” not even window-level or keyup-based page handlers)
  • Undo toast: After every replacement a toast appears with an Undo button (8 seconds, hover pauses the timer)
  • Cancel requests: Abort a running request by clicking the loading icon (⏳) or the stop button in chat, already received text is kept
  • Robust error handling: Configurable timeout (default 60s), automatic retries on network errors and rate limits (429/5xx), clear error messages (e.g. "check your API key" on 401)

πŸ”Œ Provider Support

  • Works with any OpenAI-compatible Chat Completions API:
    • Cloud: OpenAI, Mistral, Groq, Google Gemini (OpenAI-compat endpoint) and more
    • Local: Ollama, LM Studio, llama.cpp, vLLM; no API key required, data never leaves your machine
  • Flexible configuration: API URL, API key, model, temperature, timeout
  • Model list from the endpoint: "Load models" queries GET <base>/models and offers the available models as autocomplete
  • Customizable system prompts for every built-in action (e.g. {TARGET_LANGUAGE} placeholder for translation)

🌍 Internationalization

  • Full UI and default prompts in English and German (auto-selected by browser language)
  • Target language for translation is freely configurable

πŸš€ Quick Start

Prerequisites

  • Chrome (or any Chromium browser), Firefox 109+ or Thunderbird 128+
  • An OpenAI-compatible API endpoint (cloud or local)

πŸ› οΈ Installation

Install the extension directly from the official stores:

Chrome Web Store Firefox Add-ons Thunderbird Add-ons

βš™οΈ Configuration

Open the extension options (puzzle icon in the toolbar β†’ LLM Text Assistant β†’ gear icon):

Setting Description
API URL Chat Completions endpoint, e.g. https://api.openai.com/v1/chat/completions
API Key Your API key (leave empty for local endpoints)
Model e.g. gpt-4o-mini, llama3.1, mistral
Temperature Creativity (0–2, default: 0.3)
Timeout (seconds) Max wait time per attempt (default: 60). Retries twice on timeout.
Target language (Translate) Language to translate into (e.g. English, German, French)
System prompts Per-action instructions, e.g. {TARGET_LANGUAGE} placeholder for translation
Shortcuts Assign keyboard shortcuts to any action (canonical form: [Ctrl+][Alt+][Shift+][Meta+]<Key>)
Page context / max chars Whether the free-prompt chat gets page title, URL and surrounding paragraphs, and the character cap (0 = off)
Confirm before replacing Show an old→new diff and wait for confirmation before writing the result (default: off)

Local endpoint examples

Provider URL API Key
Ollama http://localhost:11434/v1/chat/completions leave empty
LM Studio http://localhost:1234/v1/chat/completions leave empty

πŸ“– Usage

  1. Mark text in an input field (or just focus the field to use the floating πŸ€– icon)
  2. Choose an action via right-click context menu, the floating menu, or a keyboard shortcut
  3. The processed text replaces the selection / field content live (streaming)
  4. Cancel: click the loading icon or "⏹ Stop" in the chat window
  5. Undo: use the button in the green toast at the bottom right

Line breaks: line breaks in the result are always preserved β€” for selections and for whole-field processing alike.

Editing after a replacement: the caret is re-anchored right after the replaced text, and all streaming helper nodes (markers) are removed when the replacement completes β€” the field stays fully editable afterwards, including in Thunderbird's compose window (v1.5.8 fixed a caret-jumps-to-the-start bug there).

Streaming in Thunderbird / framework editors (v1.5.9): results are written to the field in a single pass on completion instead of rewriting the whole field per token. Previously, per-token select-all rewrites through the editor pipeline could stack partial snapshots of the text when the editor's selection state was stale (observed in Thunderbird's compose window as repeated, shrinking copies of the mail text).

Multi-line selections (v1.5.10): the selected text in rich-text fields is read with its line breaks intact β€” results keep the original line structure instead of coming back as a single line (a regression since v1.5.0 where Range.toString() dropped breaks at <br>/block boundaries).

Teams / React editors (v1.5.14): replacement now works in Microsoft Teams chat compose (and other React/Vue/Angular-rendered editors). The floating icon appeared (v1.5.13 iframes fix) but the text was never replaced: the editor is framework-managed without any recognizable editor-library signals, so the extension took the direct-DOM write path β€” and React-style frameworks reconcile their DOM from an internal model, silently reverting un-modelled writes. Two-part fix: (1) isFrameworkManagedCE() now recognizes framework-rendered contenteditables (framework state markers on the editable or its parent wrapper) and routes all writes through the editing pipeline (beforeinput/input events the framework accepts); (2) direct writes in replaceFullTextInElement() are verified by read-back β€” if the write is reverted (synchronously or on the framework's async render schedule) and the field still shows exactly the pre-replacement text, the replacement is retried once through the editing pipeline.

Teams / CKEditor model writes (v1.5.15): v1.5.14 still failed in real Teams β€” its compose editor (CKEditor-based) takes execCommand writes into the DOM but never into its internal model, so the write looked successful and was silently restored on the editor's next render cycle (a DOM read-back cannot see the model). The one input path every model-owning editor natively rebuilds its model from is a paste event: the replacement is now written as a synthetic paste over a DOM select-all, after a short pause for the editor's async selection observer (CKEditor converts DOM selections into model selections only debounced), with a staged verified chain β€” sync pipeline write first (works for Draft.js/Lexical/ProseMirror), paste over select-all as the CKEditor path, pipeline delete + paste at the caret as recovery β€” every step verified before the next, cancelled the moment a new action or undo starts. Verified end-to-end against a real CKEditor 5 build: the replacement lands in the editor's model with line breaks intact (<p>…<br>…</p>).

πŸ†• v1.6.0 β€” page context, diff preview, model list

Page context for the free prompt: The chat window can receive more than the field's own text β€” the page title, the URL and the paragraphs immediately around the edited field are added as clearly-labelled background information. Toggle it per session in the chat header; the options page sets the default (Pass page context to the chat) and a character cap (Max page-context characters, default 600, 0 = off). Off by default: the surrounding page text is only read when the feature is switched on. The field's own content is never duplicated into the context.

Preset chips in the chat: Four ready-made instructions (Shorter, More formal, As email, Bullet list) sit above the chat input. A click drops the instruction into the input field for review β€” it is never sent automatically.

Review before replacing (optional, off by default): With Confirm result before replacing enabled, every finished result is shown as a word-level old→new diff (removals struck through in red, additions in green) and is only written into the field after you click Apply. Discard leaves the field exactly as it was — the field is not touched at all while the result is still being generated (since v1.6.4). This works for selection actions, whole-field actions and the chat's "Apply".

Model list from the endpoint: The options page has a Load models button next to the model field. It derives GET <base>/models from the configured chat-completions URL (…/v1/chat/completions β†’ …/v1/models) and fills the model input's autocomplete. Works with OpenAI, Ollama, LM Studio and llama.cpp. The request runs in the background (not subject to a page's CSP), but the endpoint still needs to allow the extension origin β€” a CORS/DNS error in the status line almost always means the local server needs --allow-origins/OLLAMA_ORIGINS.

πŸ†• v1.6.2 β€” fix: extension failed to load in Chrome

A locale placeholder was declared in the message text but not in the placeholders block (optionsModelsLoaded). Chrome aborts the entire extension load when it finds an undeclared $TOKEN$, so the extension did not appear at all in Chrome after installing from the store β€” the reason the Chrome Web Store rejected v1.6.0 and v1.6.1 as "does not work as described". Fixed by declaring the placeholder in both locales. The build now rejects undeclared placeholders, test-locale-placeholders.js covers it in CI, and tools/chrome-load-check.py loads the built package into a real Chrome to catch this class of error before submitting.

πŸ†• v1.6.4 β€” fix: confirm-before-replacing wrote the result anyway

With Confirm result before replacing enabled, the field was still overwritten while the result streamed in β€” so the text stood there already before you clicked Apply, and Discard had to write the original back (clobbering anything you typed while waiting). The gate was only attached to the finish callbacks, not to the streaming write paths: whole-field actions streamed into every non-framework field (textareas, inputs, plain contenteditables) and selection actions wrote each chunk unconditionally.

Now the decision is frozen when a run starts and checked at the write boundary: whole-field runs buffer everything and write once after confirmation, selection runs drop all non-final chunks, and the background-fallback message paths (replaceFullText/replaceSelectedText) are gated as well. Discard no longer writes anything back β€” nothing was written.

πŸ†• v1.6.5 β€” fix: page shortcuts swallowed what you typed in the chat

On pages that register their own single-key shortcuts on document, the free-prompt input took no characters at all. The page's handler skips keys when event.target is a form field β€” but for a closed shadow root the page sees target retargeted to the extension's host <div>, so the check fails and the handler calls preventDefault(), dropping the character. On the MiniKanban board (n b f a c s w t and /) the board's actions also fired: n opened the Add-Card modal and / moved the focus into the board search field, so every later character landed there.

Keystrokes typed into the add-in's own UI now stop at the extension's shadow root and never reach the page, while the add-in's own shortcuts (window-capture guard) keep working. Page handlers registered in the capture phase on window/document still run first β€” they cannot be intercepted from inside, and intercepting them would break the field itself.

πŸ”’ Privacy & Security Notes

  • No data collection: The extension collects nothing. API calls go directly from your browser to the endpoint you configured, there is no intermediate server
  • API key storage: Your API key is stored in the browser's extension storage and only sent as Authorization: Bearer header to the configured endpoint
  • Permissions: <all_urls> host permission is required to detect and modify text fields on any website; contextMenus, storage, activeTab, scripting are used for the menu, settings and text replacement
  • Page context: The optional page-context feature reads the page title, URL and surrounding text locally and sends it to your configured endpoint as part of the prompt β€” only when the feature is enabled. It is off by default.

🀝 Contributing

We welcome contributions from the community!

For code changes by third parties, please coordinate with us via email at mail@s1t5.dev before making any changes.

You can also:

  • Open an Issue for bug reports or feature requests
  • Submit a Pull Request for improvements
  • Help improve documentation

Repository structure (trunk + platform overlays)

The main branch holds all shared sources (content.js, shortcuts.js, background.js, options, locales, icons) plus the canonical manifest.json β€” the only place the version number lives. Platform-specific files live as overlays:

manifest.json                    ← Chrome manifest (canonical version)
platform/firefox/manifest.json   ← Gecko settings (gecko id, min version)
platform/thunderbird/…           ← manifest, background.js (compose
                                   injection), popup.html/js (toolbar)

tools/build.mjs assembles the three store trees into build/, injects the version into the Firefox/Thunderbird manifests, validates everything and packages the store zips/xpi into dist/:

node tools/build.mjs          # build + package all three targets
node tools/build.mjs --no-zip # verification build only
node tools/build.mjs firefox  # a single target

πŸ’– Support the Project

If you find this project useful and would like to support its continued development, you can buy me a coffee! Your support helps me dedicate more time and resources to improving the application and adding new features. While financial support is not required, it is greatly appreciated and helps ensure the project's ongoing maintenance and enhancement.

Buy Me a Coffee Ko-fi GitHub Sponsors


πŸ“„ License: GNU GENERAL PUBLIC LICENSE Version 3 (see LICENSE file)

About

A browser extension to process, translate and refine text in any input field with LLMs, directly where you type

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages