Skip to content

Repository files navigation

Glossa icon

Glossa

A local-first, cross-platform writing assistant. Run a model on-device, or connect any OpenAI-compatible endpoint (local or cloud).

Grammar, synonyms, advice, summaries, free-form rewrites and translation, with inline accept/reject on each suggestion. No login, no accounts, no backend.

License: MIT Flutter Status Platforms


⚠️ Alpha. Glossa is usable but young: it has not been released yet, and its behavior may change between versions. Bug reports and feedback are welcome.


What is Glossa?

Glossa is a writing editor that sends your text to a Large Language Model and turns its feedback into discrete, reviewable suggestions you can accept or reject one by one. It can run a model directly inside the app (on-device, via llama.cpp) with no endpoint and no API key, or point the same code at any OpenAI-compatible endpoint — a local server (llama.cpp / Ollama) or a cloud provider (OpenAI / Claude) — through the /v1/chat/completions API.

It is a pure client: there is no server to run, and no state is stored anywhere but on your device.

Features

  • Two interchangeable backends:
    • In-app model (on-device, via llama.cpp): no endpoint, no API key. Download one from a small catalog or add any Hugging Face GGUF repo and pick a quantization; downloads are managed by the app and resume if interrupted.
    • Remote endpoint: any OpenAI-compatible server, local or cloud.
    • The choice is live and mutually exclusive; the editor shows the active model.
  • Five writing modes plus a translator:
    • Grammar & syntax: discrete error items, accept or reject each one.
    • Synonyms & repetitions: variety suggestions.
    • Advice: clarity, tone and structure recommendations (not errors).
    • Summary: a concise summary in a side sheet.
    • Free prompt: transform the text from an instruction (preview, then apply).
    • Translate: source (auto-detected or chosen) to target, in a two-sheet layout.
  • Inline underlines colored by category, styled by severity (solid, wavy or dotted), clickable.
  • Deterministic writing score computed app-side from the open items, not produced by the model.
  • Smart re-check: debounced re-analysis as you type, per paragraph with content-addressed caching, so only the paragraph you edited is re-sent.
  • Decision memory: dismissed items don't reappear during auto re-check; a manual re-analysis starts fresh, so a mistaken dismissal can resurface.
  • Free language list: add any language by name, used by both corrections and the translator.
  • Editable prompts: customize the per-action instructions from Settings.
  • Light and dark themes with a centralized design system.

How it works

The design follows a few deliberate choices:

  • Structured JSON, not diffs. Instead of asking the model to rewrite the text and diffing the result (noisy), Glossa asks it to list the issues as a JSON array, each item already a discrete unit with original, replacement, category, severity and explanation.
  • Offsets are recomputed app-side. Model character offsets are unreliable, so Glossa never trusts them: it re-locates each original string inside the current text (handling multiple occurrences). See SuggestionLocalizer.
  • Prompts are in English, output follows the text. Base prompts are written in English, which behaves more consistently across models; a centralized language rule asks the model to answer in the user's language (or the one chosen in Settings).
  • No backend. The app calls the endpoint directly over HTTP. Cloud keys, if used, live only in local storage on the user's own machine.
┌──────────────────────────────────────────────┐
│              Glossa (Flutter client)          │
│  editor + overlay · panels · score · settings │
└─────────────┬───────────────────────┬─────────┘
  in-process  │                       │ HTTP POST /v1/chat/completions
  (FFI)       │             ┌─────────┴─────────┐
              ▼             ▼                   ▼
   ┌────────────────────┐  ┌─────────────────┐  ┌─────────────────┐
   │ In-app model       │  │ Local endpoint  │  │ Cloud endpoint  │
   │ llama.cpp on-device│  │ llama.cpp/Ollama│  │ OpenAI / Claude │
   └────────────────────┘  └─────────────────┘  └─────────────────┘

Supported platforms

Platform Status Notes
iOS / iPadOS universal app
Android
macOS
Windows desktop
Linux desktop (secure storage via libsecret)
Web local endpoint only (cloud blocked by browser CORS)

Getting started

Prerequisites

  • Flutter 3.44+ (Dart 3.12+) with native assets enabled (flutter config --enable-native-assets, needed by the in-app model backend).
  • A model to use, either:
    • In-app: nothing to set up — download one from Settings, no endpoint or key.
    • Remote endpoint: a llama.cpp server or Ollama locally, or a cloud provider exposing /v1/chat/completions (e.g. OpenAI).

Run

git clone https://github.com/pacio5/glossa-language-tool.git
cd glossa-language-tool
flutter pub get

flutter run -d macos     # or: chrome, <ios-simulator>, <android-emulator>

Then open Settings and choose a backend:

  • In-app: pick a model and download it (or add a Hugging Face GGUF repo), then select it. No endpoint or key needed.
  • Remote: set the endpoint URL (e.g. http://127.0.0.1:8080), an optional API key, and the model name. "Test connection" verifies and saves it.

Connecting to an LLM

In-app model

Switch the backend to In-app in Settings and download a model from the catalog, or paste a Hugging Face GGUF repo (e.g. unsloth/Qwen3.5-2B-GGUF) and pick a quantization. The model runs on-device via llama.cpp (GPU-accelerated where available); nothing leaves your machine. Larger models need more RAM and a longer first download.

Remote endpoint

The endpoint URL is normalized for you: /v1 and /chat/completions are optional and added automatically.

Local model

Run llama.cpp or Ollama and point Glossa at it. Network reachability differs by platform; the project is already configured, so you only need the right URL:

Running on Endpoint to use Why
macOS / iOS simulator http://127.0.0.1:8080 shares the host loopback
Android emulator http://127.0.0.1:8080 after adb reverse tcp:8080 tcp:8080 forwards the device's localhost to the host
Physical phone (Wi-Fi) http://<your-mac-ip>:8080 bind the server to 0.0.0.0

For the web build against a local endpoint, open CORS on the server (OLLAMA_ORIGINS for Ollama, the corresponding flag for llama.cpp).

Cloud provider

Use the provider's base URL and your API key. Cloud-from-web is blocked by the provider's CORS policy, so use the desktop or mobile build for cloud endpoints.

OpenAI: endpoint https://api.openai.com, your OpenAI key, a model name (e.g. gpt-4o-mini).

Claude (Anthropic): Glossa reaches Claude through Anthropic's OpenAI-compatible endpoint, so the same client works with no special handling. Set:

  • Endpoint URL: https://api.anthropic.com
  • API key: your Anthropic key
  • Model: a Claude model name (e.g. claude-opus-4-8)

The compatibility layer ignores the JSON-mode hint Glossa sends, but this is harmless: the prompts already require a JSON-only reply and the parser extracts it, so the writing modes work unchanged.

Configuration

Everything lives in Settings (persisted locally, no account). The API key is kept in the platform secure storage (Keychain on iOS/macOS, Keystore on Android, Credential Locker on Windows); the rest of the settings live in shared_preferences. On the web there is no OS keystore, so prefer a short-lived or scoped key there.

  • Backend: an in-app on-device model, or a remote OpenAI-compatible endpoint (mutually exclusive, switched live)
  • In-app models: download from a catalog or add a Hugging Face GGUF repo; managed and persisted on device
  • Endpoint URL, API key (optional), model name (remote backend)
  • Correction language: automatic (follow the text) or a specific language
  • Languages: a free list you can add to; feeds corrections and the translator
  • Theme: light or dark
  • Prompts: edit the per-action instructions sent to the model

Development

The project follows a small set of conventions:

  • Architecture: MVVM, feature-first (lib/features/<feature>/ with model · data · cubit · view; cross-cutting code in lib/core/).
  • State management: flutter_bloc (Cubits), immutable state with equatable.
  • Dependency injection via RepositoryProvider / BlocProvider.
  • Static analysis: very_good_analysis; flutter analyze must stay at zero issues.
  • Language: English everywhere in code, comments and UI strings.
flutter analyze        # zero issues expected
dart format lib test   # formatting
flutter test           # unit + widget tests (live integration tests skip)

The live integration tests are gated by env vars. A remote one hits a real endpoint; the embedded one downloads a small model and runs it on-device:

LLM_LIVE_ENDPOINT=http://127.0.0.1:8080 \
LLM_LIVE_MODEL=<your-model> \
flutter test test/integration/

RUN_EMBEDDED_LIVE=1 flutter test \
  test/integration/embedded_live_test.dart --timeout=none

Project structure

lib/
├─ main.dart                 # composition root (DI wiring)
├─ app.dart                  # MaterialApp + providers + theming
├─ core/
│  ├─ llm/                   # providers (remote + in-app), model manager/catalog
│  └─ ui/                    # theme + breakpoints
└─ features/
   ├─ editor/                # the editor: cubit, analysis, localizer, views
   └─ settings/              # endpoint/model/language/theme/prompts

Roadmap

Core functionality is complete. Remaining items (see plan/):

  • Embedded inference: bundle llama.cpp directly into the app (via Dart FFI or a native plugin) so it can run a local model on-device, with no external endpoint to set up. The current OpenAI-compatible client stays as an option for cloud or shared models.
  • Model benchmarking across IT / EN / ES
  • Release packaging per platform
  • Optional richer iPad two-column layout

Contributing

Issues and pull requests are welcome. Please keep flutter analyze clean, all tests passing, and code/comments in English.

License

MIT © Elia Pacioni

About

Glossa is a writing editor that sends your text to a Large Language Model and turns its feedback into discrete, reviewable suggestions you can accept or reject one by one. It points the same code at either a local model (llama.cpp / Ollama) or a cloud provider (OpenAI / Claude) through the common OpenAI-compatible API.

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages