Skip to content

Proposal: Rewrite with AI for selected text (tones, local or your own cloud model) #860

Open

Description

@senadaruc

Hi Jacob and Akram 👋 Here's another feature I've built on my fork: Rewrite with AI. Select some text you wrote, pick a tone, and Cotabby rewrites it in place. Before I open PRs I'd like to know whether you want it in Cotabby, and in what shape.

The code is in my fork, in senadaruc#1 (branch feat/selection-rewrite). It's about 2,800 lines of app code and 1,600 lines of tests.

What it does

You write a message, select it, and get it back friendlier, more formal or simply without the typos. You never copy it into a chat window and paste it back.

  • Trigger. While text is selected in a field, the field-edge Cotabby icon turns into a wand. Click it or press ⌃⌥R.

  • Tones first. The popup opens on five tones:

    • Family
    • Friendly
    • Close friend (relaxed, with slang)
    • Formal
    • Customer

    A Fix spelling & grammar tick box sits next to them. It is on by default and remembered. There is also a field for your own instruction ("make it sound less pushy").

  • Preview, then refine. The rewrite streams into a preview with Replace, Copy and Retry. Shorter and Longer refine that preview and keep the tone.

  • Replace in place. Replace puts the rewrite exactly where the selection was, in the same field.

  • Same language. A Turkish draft stays Turkish and a German one stays German. The detected language is named in the prompt, because without that Apple's model answered non-English drafts in English.

How it works

  1. Selection → button. FocusTracker snapshots now carry the selected text and its range in the field. A pure gate, SelectionRewriteEligibility, decides whether the wand may appear. The rules:

    • at least two words, at most 2,000 characters;
    • never in password fields, terminals or terminal-screen hosts;
    • only in apps where Cotabby is on.

    A selection that is too long gets a message instead of silently getting no button.

  2. Popup. It's a non-activating panel that takes the keyboard when it opens, so pressing Return can never reach a chat app and send the half-edited message. It sits where the field-edge icon was and stays on screen.

  3. Prompt. RewritePromptBuilder builds the prompt from the action, the tone, the fix tick box and the detected language. It has two shapes: instruct-style for Apple Intelligence and cloud models, example-style for the llama base models. The selected text is passed as quoted data, never as instructions, so a selection reading "ignore the above and…" is just rewritten.

  4. Model. RewriteEngine sends the prompt to the model chosen in Settings → Rewrite:

    • On this Mac (default). Apple Intelligence when it's available. Otherwise the selected llama model, labelled "basic quality" because base models follow tone instructions less well. A rewrite shares the llama runtime with autocomplete: it waits for an in-flight suggestion and resets the prompt cache once.
    • A cloud model of your choice. Claude goes through Anthropic's own Messages API: raw HTTP, streamed, low effort, refusals reported as errors. OpenAI, Gemini, Mistral, Groq, OpenRouter, xAI, DeepSeek and any custom endpoint go through OpenAI-compatible chat completions. Load models fills a model picker from the service, and each service's key is stored in the Keychain.
  5. Clean-up. RewriteOutputCleaner strips what models wrap around an answer: "Here is the rewritten text:", code fences, quotes around the whole answer. It keeps the selection's own leading and trailing spaces. Output that is empty, or too long to be a rewrite of the selection, is rejected. Such a result shows "Couldn't rewrite this — try again." and never replaces anything.

  6. Safety checks before Replace.

    • Same field, same text. SelectionReplaceValidator pastes only if the focus is still the same field, with the same text, at the same range. If anything moved, the rewrite goes to the clipboard and a notice says so, so nothing gets pasted over the wrong text. After pasting, it reads the field back to confirm the result.
    • No made-up numbers or names. If the rewrite contains a number or a name that isn't in your text, the preview says exactly what changed, for example "The number changed: you wrote 1000, the rewrite says 100." Plain Replace and Return are then disabled, and a deliberate Replace anyway click is needed.

Privacy

  • Local by default. The default provider runs on the Mac, and nothing leaves it.
  • Cloud only on request. Cloud is used only after the user picks a service and enters their own key. Only the selected text and the instruction are sent, never the rest of the field, the screen or other apps.
  • Keys in the Keychain. The named services are called over HTTPS, and each service's key is its own Keychain item, never stored in UserDefaults.

Possible PR plan

I'd split it into a few reviewable PRs on top of main:

  1. Selected text and its range in the focus snapshot, plus the eligibility gate (pure, with tests).
  2. Prompt building, output cleaning and replace validation (pure, with tests).
  3. The rewrite engine with the local provider: Apple Intelligence, then llama.
  4. The popup, the wand on the field-edge icon, ⌃⌥R and Settings → Rewrite.
  5. Cloud services: the Claude Messages API, the OpenAI-compatible services, model lists and Keychain keys.

My fork's version can also draw on the Memory module from #850 ("Use memory": facts from the same conversation and your own writing style). That part would wait for your answer on #850. The PRs above would ship without it.

Questions for you

  • Do you want selection rewrite in Cotabby at all?
  • Is a cloud option acceptable to you as strictly opt-in, bring-your-own-key? Or should rewrite stay local-only upstream?
  • Is the field-edge icon a good place for the trigger, or would you rather have only the shortcut?

Happy to adjust the design to what fits Cotabby. Thanks!

Activity

  1. senadaruc commented on Oct 14, 2026

    @senadaruc
    ContributorAuthor

    The PR is up: #869. It's the version without conversation memory, as suggested above, and it has a master switch (Settings → Rewrite → Rewrite Selected Text) that turns the wand and ⌃⌥R off entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions