Skip to content

AI Assistant

Michael Dohmen edited this page Aug 15, 2026 · 2 revisions

AI Assistant

Switched off by default. While off, the AI integration opens no network connection at all — there is no second way out and no hidden fallback destination. Turning it on is an explicit, visible choice in Settings.

The one other thing that reaches the network is the usage counter, which is on by default and likewise a visible switch. Turn both off and the file is completely silent.

What it's for

A docked chat panel that can answer questions about the records in the open file, using any OpenAI-compatible endpoint you point it at — OpenAI itself, Azure AI Foundry, LiteLLM, a self-hosted vLLM or Ollama, OpenRouter, or a corporate gateway in front of any of those. On an explicit instruction, it can also propose creating, updating or deleting records — which you review and approve before anything touches the data.

The CORS caveat you will hit

From a file opened via file://, the browser's request origin is the literal string "null". The endpoint has to explicitly allow that origin via CORS, or the browser blocks the request before anything is sent. In practice this means:

  • Direct calls to api.openai.com will not work — OpenAI's API doesn't allow the null origin.
  • You need a proxy in front that does (LiteLLM, Azure API Management, a small serverless function, your own gateway).
  • If a request never leaves the browser, the error message says so explicitly rather than leaving you with a blank console and a generic "failed to fetch."

Dialect negotiation

"OpenAI-compatible" is a family of dialects, not a single standard. The differences that actually bite in practice:

  • Current reasoning models require max_completion_tokens and reject the older max_tokens parameter; many proxies and older models are the reverse.
  • Some models reject a non-default temperature outright.
  • Some gateways expect the system instruction under a developer role, or don't support a system role at all.
  • Azure deployments often need /v1 appended to the base URL, or an api-version query string preserved exactly.

Rather than requiring you to know your specific endpoint's dialect up front, the client:

  1. Starts with the broadest, most conservative variant (max_tokens, temperature sent, system role as system).
  2. On a 400/422 response, reads what the endpoint objects to out of the structured error body (or, failing that, a keyword match against the field names actually sent) and adapts: swaps the token parameter, drops temperature, changes the system role, or drops whichever field was named.
  3. Retries — up to six attempts — narrowing down the working combination.
  4. On a 404/405, retries once with /v1 appended to the path before giving up on that route.
  5. Stores the result (ai.dialect in settings) once a call succeeds, so the next call — and the next time the file is opened — gets it right on the first try. Settings → AI integration → "Negotiated dialect" shows what was learned, with a reset button for after you switch models.

None of this is streamed: one request, one response. That keeps the client small and makes failures unambiguous — there's no partial-stream state to reason about when something goes wrong.

What travels with every question: context modes

Three choices, in Settings → AI integration → "Context sent along":

Mode What the model sees
View (default) Only the currently filtered/sorted table view
All Every record in the file
Aggregates Only counts — total, overdue, the schema's total field, per-facet breakdowns — no individual records at all

Aggregates mode exists for exactly the case where you want a quick "what's the state of things" answer without handing over every record's contents. Whichever mode is active, the context also carries the schema's shape (field names, types, allowed enum values) so the model knows what it's looking at, and any attached files (see below).

If the record set is large enough that its JSON would blow past the context budget, it's truncated to the first N records with a note in the prompt saying so — never silently.

Attachments

Text-ish files (.txt, .md, .csv, .json, .yaml, .log, and similar) can be dropped or picked into the chat and are appended as extra context on the next question. Two guards before a file is accepted: its extension/MIME type has to look like text, and its content is checked for embedded null bytes — a common tell for a binary file wearing a harmless-looking extension. History and attachments live only in the browser session; neither is written back into the saved file.

Changes are proposals, not commands

This is the part worth understanding in detail, because it's the whole reason the AI integration is safe to turn on for real data:

  1. On an explicit instruction, the model appends a fenced code block (tagged aktionen, tolerant of actions/json too) containing a JSON array of operations: create, update, or delete.
  2. Nothing from that response is trusted directly. src/lib/actions.js validates every single operation against the current SCHEMA before anything is applied:
    • Unknown fields are dropped and named in the result, not silently ignored.
    • Enum values are matched case/whitespace-tolerantly against field.values — anything outside that set is rejected and the allowed values are listed back.
    • Numbers and dates are type-checked; malformed values are rejected with the field named.
    • IDs for updates/deletes must resolve to an existing record; new IDs are always assigned by the app (uid()), never accepted from the model.
  3. You see the proposal before it's applied — a human-readable list, one line per operation, built from the same validation pass (so what you approve is exactly what will happen, not a paraphrase). Only after you click Apply does anything change; Discard throws it away. An "apply without asking" setting exists for automation-style use, off by default.
  4. The result — what was applied, and what was rejected and why — is shown back to you, both in the chat and, if you asked for changes via chat, in the same review list.

What the model can't do

  • It can't act without an explicit instruction — plain questions never produce action proposals.
  • It can't invent record IDs, bypass field types, or write values outside an enum's allowed set.
  • It can't reach the network on its own; every call goes through the one client in src/lib/ai.js, which only ever talks to the single endpoint configured in Settings.

API keys are not stored by default

If AI is enabled but no key is stored with the file, opening it prompts once for a key (session-only by default) and offers to switch the integration off entirely instead. A "store the key" toggle in Settings makes the choice explicit: with encryption on, the key goes into the encrypted envelope; without it, it sits in plain text in a file that may get passed around — the settings hint says so directly rather than leaving you to reason it out. See Security and Encryption for how the envelope itself works.

What stays English regardless of interface language

The instructions and schema description sent to the model (buildInstructions, buildContext in src/lib/ai.js) are always in English, independent of whatever interface language is selected — current models are most reliable in English, and this text is never read directly by a user, only by the model. Everything a user reads — the chat dock, the proposal review, error messages — is translated; see Interface Languages.

Clone this wiki locally