-
Notifications
You must be signed in to change notification settings - Fork 0
AI Assistant
Switched off by default. While off, the application opens no network connection at all — there is no second way out, no telemetry, nothing phoning home. Turning it on is an explicit, visible choice in Settings.
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.
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.comwill not work — OpenAI's API doesn't allow thenullorigin. - 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."
"OpenAI-compatible" is a family of dialects, not a single standard. The differences that actually bite in practice:
- Current reasoning models require
max_completion_tokensand reject the oldermax_tokensparameter; many proxies and older models are the reverse. - Some models reject a non-default
temperatureoutright. - Some gateways expect the system instruction under a
developerrole, or don't support a system role at all. - Azure deployments often need
/v1appended to the base URL, or anapi-versionquery string preserved exactly.
Rather than requiring you to know your specific endpoint's dialect up front, the client:
-
Starts with the broadest, most conservative variant (
max_tokens, temperature sent, system role assystem). - 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.
- Retries — up to six attempts — narrowing down the working combination.
-
On a 404/405, retries once with
/v1appended to the path before giving up on that route. -
Stores the result (
ai.dialectin 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.
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.
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.
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:
- On an explicit instruction, the model appends a fenced code block (tagged
aktionen, tolerant ofactions/jsontoo) containing a JSON array of operations:create,update, ordelete. -
Nothing from that response is trusted directly.
src/lib/actions.jsvalidates every single operation against the currentSCHEMAbefore 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.
- 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.
- 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.
- 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.
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.
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.