Skip to content

feat(editor): add opt-in AI inline completion - #1455

Merged
NewtTheWolf merged 3 commits into
TabularisDB:mainfrom
lejyfps:feat/1414-ai-inline-completion
Nov 9, 2026
Merged

NewtTheWolf merged 3 commits into
TabularisDB:mainfrom
lejyfps:feat/1414-ai-inline-completion

Conversation

@lejyfps

@lejyfps lejyfps commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1414.

SQL consoles can now display optional AI continuations as Monaco ghost text. The feature defaults off and uses the provider/model already selected in AI settings, including Ollama and custom OpenAI-compatible local endpoints. Tab/Escape retain Monaco's normal acceptance/dismissal behavior, autocomplete and indentation keep priority, and suggestions contain only an insertion: accepting them never invokes query execution.

Enabling the setting discloses the payload: at most 4,000 characters before the cursor, 1,000 after it and 8,000 characters of already-cached table/schema names. Metadata is selected for the active connection/database/schema and allowlists names only. No result rows, table comments, connection credentials or additional database queries are added. SQL literals remain part of the user's disclosed editor context.

Requests debounce for 600 ms, stop during IME composition and cancel on edits, cursor/model/editor/context/provider changes or focus loss. Backend cancellation is scoped by runtime and web session, handles cancellation arriving before generation, and drops the provider future. Requests time out after 20 seconds; errors leave normal editing intact and back off for 30 seconds. Version/cursor checks discard stale responses. Provider responses must contain a bounded JSON completion string, preserving whitespace without accepting commands or extra edits. The inline parser also applies the existing response cleaner so fenced JSON works through the ChatGPT path as well as the other provider helpers.

Desktop and web expose the same implementation through two typed sensitive commands. The persisted opt-in is checked in the backend too. Built-in and external database drivers use the same path, with their declared SQL dialect and cached metadata; no driver ids are special-cased. All 13 locales include the setting, privacy disclosure and key guidance. Embedded notebook/modal editors remain unchanged in this milestone.

Validation:

  • 168 focused frontend tests passed across completion/cancellation, lifecycle/config changes, context isolation, IME/widgets/selection/read-only behavior, failures, editor shortcuts, AI settings/defaults, settings hydration, history and transport contracts.
  • Four production inline-module tests passed for limits/response parsing, session ownership, in-flight cancellation, early cancellation and bounded request bookkeeping. They ran through an isolated Cargo harness with parent provider/runtime dependencies stubbed; Clippy passed with warnings denied.
  • Repository-wide ESLint and Prettier passed, along with app/E2E typechecks, theme tokens, frontend boundaries and git diff --check.
  • Added shared application opt-in/persistence/cancellation and web RPC routing regressions. Added a Playwright test using the real Monaco UI and a mocked provider response for automatic ghost text, Escape, Tab, one-step undo and absence of execution requests.
  • Full cargo check --locked --no-default-features --bin tabularis is locally blocked by vendored OpenSSL requiring a missing Perl executable. Full application/RPC tests and the new browser test have not been run locally and require CI. No paid/live provider requests or interactive desktop smoke test were performed.

GitNexus upstream impacts and staged change detection were run. Its final graph classified all 300 flows as CRITICAL, including unrelated grid flows attributed to generic test-interface methods. The actual 35-file diff and command/editor call sites were inspected directly; the graph's broad classification is not treated as reliable coverage.

Coordination with #1337: this PR targets current main and reuses its existing provider dispatcher, without adding a provider adapter or credential store. #1337 changes the signatures of resolve_model and dispatch_provider; whichever PR lands second must rebase and adjust the two calls in ai/inline.rs (plus resolve shared AI settings/command registration conflicts). No provider-refactor commits are bundled here.

Manual check: enable AI inline completion in AI settings after reviewing the disclosure, choose a configured provider/model, connect to a database and type in a SQL console. Ordinary completion widgets take priority. Accept visible ghost text with Tab, verify one Undo restores the previous SQL, and confirm no query runs. Repeat with Escape, a tab/connection/model switch, IME input and an unavailable provider. Disable the setting and confirm provider requests stop.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Follow-up to the non-blocking review observations: the full GitHub CI, including Rust and all three browser engines, passed on 257be94. The fenced-JSON normalization regression was reproduced locally and now passes with the other three backend harness tests and Clippy. The follow-up commit still requires its fresh CI run. The provider-side token budget remains a shared-dispatcher improvement; the 20-second timeout is not a token or cost cap.

@lejyfps
lejyfps marked this pull request as ready for review November 9, 2026 12:54
@kilo-code-bot

kilo-code-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review of 257be94..eeb3a05. No inline comments were posted. Read-only review; no code or tests were run.

What changed since the last review

  • src-tauri/src/ai/inline.rs: completion() now passes the provider response through the existing clean_response helper before serde_json::from_str. This addresses the earlier non-blocking observation about markdown-fenced JSON from local or custom providers. The helper is private to ai.rs but reachable from the child inline module via super::.
  • Strict validation is intact: the 16,384-byte size check runs before cleaning, and deny_unknown_fields, the 2,000-character limit and the NUL check still apply after it. Whitespace inside the completion string value is preserved, because only the outer response is trimmed.
  • src-tauri/src/ai/inline_tests.rs: new test covers fenced responses (no tag, json, JSON, CRLF), preservation of leading and trailing whitespace in the completion, and rejection of unknown fields, NUL characters, non-JSON text and oversized completions inside a fence.
  • A comment documents that output tokens use shared provider defaults until the dispatcher exposes a budget (the other earlier observation).
Files Reviewed (2 files)
  • src-tauri/src/ai/inline.rs
  • src-tauri/src/ai/inline_tests.rs
Previous Review Summary (commit 257be94)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 257be94)

Status: No Issues Found | Recommendation: Merge

Reviewed commit 257be94. No inline comments were posted. The review was limited to reading the diff and the surrounding code; no code or tests were run (read-only mode).

What was checked

  • The opt-in is enforced in the backend as well as the UI. complete rejects the request unless both ai_enabled and ai_inline_completion_enabled are persisted as true.
  • Request limits match on both sides: 4,000 characters of prefix, 1,000 of suffix and 8,000 of schema. The frontend slices by UTF-16 units and the backend counts chars(), so the frontend can never exceed the backend limit.
  • Cancellation:
    • It is keyed by config directory, web session and request id, and the id charset and length are validated.
    • Early cancels are tombstoned, with a 60 s expiry and a 128-entry cap.
    • The provider future is dropped through Abortable and wrapped in a 20 s timeout.
  • The ghost text is inserted through Monaco's own inline suggest, and nothing in the path runs a query.
  • Responses are strictly parsed (deny_unknown_fields, with length and NUL checks).
  • inlineTables never lets one database inherit another's cached names.
  • The desktop and web transports are both wired, and the inlineSuggest.enabled option updates dynamically through the options prop.

Non-blocking observations (not posted inline)

  • completion() requires a bare JSON object. Local models served through Ollama or custom OpenAI endpoints often wrap JSON in markdown fences. Such responses would fail parsing and trigger the 30 s client back-off, so users may see "no suggestions" without an explanation. Tolerating a fenced block, or sending a JSON response mode where the provider supports one, would improve reliability.
  • The shared generate_* helpers have no per-request token cap except Anthropic's 1024. The 2,000-character limit is enforced only after the full response arrives, so slow or long generations are bounded by the 20 s timeout alone.
  • complete_ai_inline takes provider and model from the request rather than from the persisted config. This matches the existing generate_ai_query behaviour, but a web client can choose any configured provider.
  • The PR description notes that the full Rust and web RPC tests and the new Playwright test were not run locally and rely on CI.
Files Reviewed (35 files)
  • e2e/web/tests/ai-inline-completion.spec.ts
  • packages/web-ui/src/api/contract.ts
  • packages/web-ui/src/components/settings/AiTab.tsx
  • packages/web-ui/src/components/ui/SqlEditorWrapper.tsx
  • packages/web-ui/src/contexts/SettingsContext.ts
  • packages/web-ui/src/hooks/useAiInlineCompletion.ts
  • packages/web-ui/src/i18n/locales/*.json (13 locales)
  • packages/web-ui/src/pages/Editor.tsx
  • packages/web-ui/src/types/ai.ts
  • packages/web-ui/src/utils/aiInlineCompletion.ts
  • packages/web-ui/tests/hooks/useAiInlineCompletion.test.tsx
  • packages/web-ui/tests/utils/aiInlineCompletion.test.ts
  • src-tauri/src/ai.rs
  • src-tauri/src/ai/inline.rs
  • src-tauri/src/ai/inline_tests.rs
  • src-tauri/src/application/ai.rs
  • src-tauri/src/application/ai/tests.rs
  • src-tauri/src/application/persistence.rs
  • src-tauri/src/config.rs
  • src-tauri/src/transport/desktop/ai.rs
  • src-tauri/src/transport/desktop/mod.rs
  • src-tauri/src/transport/web/rpc.rs
  • src-tauri/src/transport/web/rpc_tests.rs

Reviewed by claude-sonnet-5.5 · Input: 254 · Output: 1.4K · Cached: 168.1K

Review guidance: REVIEW.md from base branch main

@NewtTheWolf NewtTheWolf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making completion opt-in on both sides and keeping accepted text separate from query execution. I reviewed eeb3a051d4d3c123fc0fb5dc466cb3b939902725, including the active-namespace metadata selection, cancellation ownership, editor lifecycle and fenced-response handling.

The 83 targeted frontend tests and 23 matching Rust tests passed, as did typecheck, targeted source ESLint and the production frontend build. I found no evident issue in this pass. I will exercise the actual Monaco ghost text, Tab/Escape/undo, context switches and the disabled/failure paths in the real app, then run the full required checks before merging. Provider fixtures will stay local.

@NewtTheWolf

Copy link
Copy Markdown
Collaborator

Tested eeb3a051d4d3c123fc0fb5dc466cb3b939902725 in the real Linux GTK/WebKitGTK app through the actual Rust dispatcher and a local Ollama HTTP fixture, with an isolated secret store. Ready to merge.

Exercised automatic ghost text, Escape, Tab, one-step undo, normal autocomplete/indentation priority, a tab switch during a delayed response, provider failure/backoff and disabled frontend/backend opt-in. Query history stayed empty; acceptance never produced a result set. Captured HTTP payloads contained only the bounded SQL context, dialect and cached table names, with no result-row data. pnpm lint && pnpm typecheck && pnpm test:all passed, including 8,651 frontend tests; GitHub checks are green. No live model or paid API request was used. The WebKit clipboard permission messages match the official-main reproduction recorded in #1452.

@NewtTheWolf
NewtTheWolf merged commit fcf7055 into TabularisDB:main Nov 9, 2026
15 checks passed
Felixkw12 added a commit to Felixkw12/tabularis that referenced this pull request Nov 10, 2026
Resolves the conflicts with API Route (TabularisDB#1459) and the inline completion
and optional-key changes (TabularisDB#1455, TabularisDB#1457). Both providers are kept in every
list, with Opper after Requesty and API Route after Opper. In ai.rs the
Opper model fetch stays as step 7, followed by main's new custom-openai
discovery and the API Route catalog. Inline completion goes through
dispatch_provider, so it reaches Opper without further changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: SQL editor: opt-in AI inline completion

2 participants