Skip to content

feat(ai): custom prompt templates and global instructions with loading-consistency hardening#4064

Open
Abeautifulsnow wants to merge 11 commits into
t8y2:mainfrom
Abeautifulsnow:feat/custom-prompt-templates
Open

feat(ai): custom prompt templates and global instructions with loading-consistency hardening#4064
Abeautifulsnow wants to merge 11 commits into
t8y2:mainfrom
Abeautifulsnow:feat/custom-prompt-templates

Conversation

@Abeautifulsnow

Copy link
Copy Markdown
Contributor

变更说明

feat(ai): custom prompt templates and global instructions with loading-consistency hardening

Body

Summary

Two-layer custom prompt system for the DB Assistant: global instructions (always-on, injected into every AI conversation) and scenario prompt templates (multi-select per panel, stored in a prompt_templates table). The injection path is unified through buildCustomInstructionLines, placed after behavior rules and before schema context. Baseline output stays byte-identical when no custom content is configured.

Follow-up commits harden loading consistency: a request sent during initialization now awaits the single in-flight load, a failed load blocks the request with a localized error rather than silently omitting instructions, stale template selections are cleaned up on delete, and template checkboxes reset when switching connection/database.

What Changed

Core Feature (392e659)

  • Global custom instructions stored in app_state under the ai_global_custom_instructions key and always injected into every AI conversation.
  • Scenario prompt templates — multi-select per assistant panel serompt_templates` table with backend-authoritative validation:
    • Unicode character-count length limits matching str::chars().count()
    • Case-insensitive duplicate name detection
    • Upsert preserving created_at
  • AppError now carries an HTTP StatusCode so validation errors return 400 and missing resources return 404 instead of a blanket 500.
  • Template data is intentionally excluded from cloud sync.
  • New backend endpoints under prompt_template.rs for CRUD, new Taure (promptTemplateStore.ts), and settings UI(EditorSettingsDialog.vue).

Review Fixes (a569fe8)

  • C02saveGlobalInstructions now stores the backend-trimmed value in the reactive store, matching what init() would return on next load.
  • S01confirmDeleteTemplate clears templateDeleteConfirm in a finally block so the reference doesn't survive a failed deletion.
  • S02promptTemplateStore gains ensureLoaded() for idempotent retry; AiAssistant.vue watches showTemplateSelector to call it, so the selector self-heals when the backend was unavailable during App mount.

UX / Selection State (dba2328)

  • Switching to a different connection or database resets scenario template checkboxes to empty. Global instructions are unaffected (always-on by design).

Loading-Consistency Guard (7b9ba0c)

  • AiAssistant.send() awaits the single in-flight ensureLoaded() before mutating conversation state or invoking the agent stream.
  • A failed load blocks the request with a localized error rather thanystem prompt.
  • Unicode character counting via Array.from() matches Rust str::chars().count() for consistent frontend/backend length limits.
  • Each assistant panel watches the template collection and filters out deleted template IDs from its active selection.

AppError Refactoring (part of 392e659)

AppError was changed from a tuple struct (AppError(String)) to a named-field struct that carries an HTTP StatusCode alongside the message. This enables the new prompt-template endpoints to return 400 (validation failure) and 404 (not found) instead of a blanket 500 for everything.

This is a purely mechanical change: every existing call site across ~30 route files was updated from .map_err(AppError) / AppError("...") to .map_err(AppError::from) / AppError::from("..."). The default From<String> and From<&str> impls still produce a 500, so no existing route behavior changed — only the new prompt-template code uses bad_request() and not_found().

Files Changed

Layer Key files
Backend (Rust) crates/dbx-core/src/prompt_template.rs, crates/dbx-core/src/storage.rs, crates/dbx-web/src/routes/prompt_template.rs,
crates/dbx-web/src/error.rs, `src-tauri/src/commands/prompt_templat
Frontend (Vue/TS) apps/desktop/src/components/editor/AiAssistant.vue, EditorSettingsDialog.vue, apps/desktop/src/stores/promptTemplateStore.ts, apps/desktop/src/types/promptTemplate.ts, apps/desktop/src/lib/ai/ai.ts
i18n 7 locale files (en, es, it, ja, pt-BR, zh-CN, zh-TW)
Tests packages/app-tests/aiCustomPrompt.test.ts, packages/app-tests/promptTemplateStore.test.ts

Testing

  • Unit tests: promptTemplateStore.test.ts covers store initialization, CRUD, and the ensureLoaded retry path.
  • Integration tests: aiCustomPrompt.test.ts covers prompt constom instructions, template injection ordering, global-instructioninclusion, and Unicode character counting.
  • Type-check / lint / format / Rust checks: all passing.

Screenshots / UX

Settings dialog — Global instructions tab

A dedicated section in Editor Settings for composing and previewing always-on global instructions.

Settings dialog — Scenario templates tab

List-based CRUD for named prompt templates with per-template character limits enforced at both frontend and backend.

Assistant panel — template selector

Multi-select checkboxes above the chat input; selection resets on connection/database switch; stale IDs are silently filtered.

Breaking Changes

None. Existing conversations and prompt paths are unchanged when no custom instructions or templates are configured.

变更类型

  • 新功能
  • Bug 修复
  • 性能优化
  • 代码重构
  • 文档更新
  • CI / 构建

涉及前端

  • 本 PR 涉及前端改动,已附截图/录屏(见下方)
24d7f11f-3543-4247-8336-71491fcd48d3 b479c6c7-d9ee-444e-a977-fdfa2440669c e34ba76a-3a65-4a72-81f7-67e26e86df08

验证

  • make check 通过
  • make cargo-check-fast 通过
  • 相关测试通过

关联 Issue

Related #3975

Two-layer custom prompt system for the DB Assistant:
- Global custom instructions: always injected into every AI conversation,
  stored in app_state (ai_global_custom_instructions key)
- Scenario prompt templates: multi-select per panel session, stored in a
  new prompt_templates table with backend-authoritative validation
  (char-based length limits, case-insensitive duplicate names, upsert
  preserving created_at)

Injection is unified across relational and vector database prompt paths
via buildCustomInstructionLines, placed after behavior rules and before
schema context; baseline output stays byte-identical when no custom
content is set. Template data is intentionally excluded from cloud sync.

AppError now carries a StatusCode so validation errors return 400 and
missing resources 404 instead of blanket 500.
…nt failure

C02: saveGlobalInstructions now stores the backend-trimmed value in
the reactive store (matching what a subsequent init() would return).

S01: confirmDeleteTemplate clears templateDeleteConfirm in finally
so the reference does not survive a failed deletion.

S02: promptTemplateStore gains ensureLoaded() for idempotent retry
on prior init failure; AiAssistant.vue watches showTemplateSelector
to call it, so the selector gracefully self-heals when the backend
was not ready during App mount.
When the user switches to a different connection or database, the
scenario template checkboxes now reset to empty — a fresh database
context warrants a clean template selection rather than inheriting
the previous connection's choices. Global instructions are unaffected
(they are always-on by design).
…mplates

# Conflicts:
#	crates/dbx-web/src/routes/agents.rs

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The Rust changes do not currently compile under the repository CI, so this cannot be merged yet.

AppError was changed from a tuple struct to named fields, but the test targets still contain 21 accesses through error.0 across routes such as sql_file.rs and table_import.rs. The same CI run also fails Clippy on the newly added unused name25 variable in storage.rs.

Please update all affected test call sites to use the named field/API, remove the unused test variable, and rerun the full Rust CI until both rust-fmt-clippy and rust-test pass.

The reset watcher returned a fresh array literal `[connection.id, database]`
as its getter, so Vue's Object.is comparison always saw a change and cleared
the selection on any dependency invalidation — e.g. the 30s backup scheduler
replacing connection objects mid agent-run — even when id/database were
unchanged. Return a stable primitive key so the reset fires only on real
connection/database switches.
AppError became a struct with named `message`/`status` fields, but the route
tests still accessed the error string via the old tuple index `.0`, breaking
compilation (E0609). Switch those accesses to `.message`. Also drop a dead
shadowed `name25` binding in dbx-core storage tests that tripped
`-D warnings` in CI.
@Abeautifulsnow
Abeautifulsnow requested a review from t8y2 July 21, 2026 10:24
@Abeautifulsnow

Copy link
Copy Markdown
Contributor Author

The Rust changes do not currently compile under the repository CI, so this cannot be merged yet.

AppError was changed from a tuple struct to named fields, but the test targets still contain 21 accesses through error.0 across routes such as sql_file.rs and table_import.rs. The same CI run also fails Clippy on the newly added unused name25 variable in storage.rs.

Please update all affected test call sites to use the named field/API, remove the unused test variable, and rerun the full Rust CI until both rust-fmt-clippy and rust-test pass.

已经全量修复。

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

request changes: the previous Rust CI blocker is fixed, but three consistency issues remain.

  1. apps/desktop/src/components/editor/EditorSettingsDialog.vue:1971 can copy an empty value while the fire-and-forget prompt store initialization is still running. When the load later succeeds, the textarea is not refreshed, so pressing Save can silently erase existing global instructions. Please await the load when entering the AI tab and add a delayed-load regression test.

  2. apps/desktop/src/components/editor/AiAssistant.vue:1457 awaits ensureLoaded() before establishing a synchronous send guard. Two rapid submissions can both pass the initial isGenerating check and resume into separate agent runs. Please acquire a send-pending guard before the first await and test two concurrent sends against a deferred load.

  3. crates/dbx-core/src/storage.rs:1637 relies on SQLite LOWER() for backend-authoritative case-insensitive uniqueness, but the frontend applies JavaScript Unicode case conversion. Names such as Ä规则 and ä规则 can therefore coexist in storage while the frontend treats them as duplicates. Please use a consistent normalization/comparison strategy and cover non-ASCII case pairs.

Evidence: SQLite built-in scalar functions:

“all ASCII characters converted to lower case.”

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.

2 participants