feat: rescope to AI-first DB harness with multi-DB chat agent
Removes enterprise/DBA features and replaces the marginal AI bar with a central chat agent that has progressive-discovery tools, cross-session memory, saved-query reuse, and inline result actions. Adds ClickHouse support alongside PostgreSQL/Greenplum. Cleanup - Drop ~10k LOC of advanced features: Docker, Snapshots, Validation, Index Advisor, Role/User Management, Data Generator, ERD, Lookup. - Trim deps: drop @xyflow/react, dagre, @types/dagre; cut tokio features to rt-multi-thread/sync/time/net/macros. - Remove unused TuskError variants and dead helpers (topological_sort, invalidate_schema_cache). Multi-DB (PostgreSQL + ClickHouse) - New src-tauri/src/db/ module: ChClient (HTTP-based, reuses reqwest), sql_guard (cross-flavor read-only whitelist with 8 tests). - ConnectionConfig gains db_flavor and secure fields with serde defaults for backwards-compatible connections.json. - All connection/query/schema/data commands dispatch by flavor; CH covers connect, execute_query, list_databases/schemas/tables/views/ columns/completion_schema, paginated table fetch. - Frontend: dbCapabilities matrix, ConnectionDialog engine selector with port auto-swap and HTTPS toggle, SqlEditor switches to StandardSQL dialect for CH, TableDataView surfaces CH connections as read-only. AI-first chat agent - New src/components/chat/ panel with composer, message rendering, collapsible tool-call/result blocks, top-level ErrorBoundary. - Backend agent loop in commands/chat.rs with strict-JSON tool protocol. Nine tools: list_databases, list_tables, get_columns, switch_database, run_query, remember, save_query, find_queries, final. Forgiving parser accepts both flat and nested-input shapes. - Compressed history: only the last 4 run_query results carry sample rows (≤10, cells truncated to 200 chars) into LLM context; older results marked omitted. - System prompt uses lite OVERVIEW (DB list + active-DB tables only) instead of full DDL — schema details are loaded on demand via get_columns. CH OVERVIEW shows cross-DB tables since CH allows db.table queries. Cross-session memory (F1) - Per-connection markdown file at app_data_dir/memory/<connection_id>.md, 16KB cap with oldest-block eviction. Agent appends via remember() tool; the file is injected into LEARNED NOTES section of every system prompt. - New Memory sidebar tab with editable textarea, badge for note count, empty-state with template. Edits picked up on the next agent turn. Saved-query reuse (F2) - Tools save_query and find_queries scoped to current connection. save_query attaches a UUID + timestamp; find_queries returns top 10 matches with SQL preview ≤500 chars. - Storage shared with the sidebar Saved panel. Inline result actions (F3) - run_query result block in chat gets Open-full (90vw × 80vh modal with full ResultsTable, no row cap) and Export (reuses ExportDialog for CSV/JSON via existing exportCsv/exportJson commands). Verification - cargo check clean, zero warnings. - cargo test --lib: 50 pass (20 chat parser + 4 memory + 8 sql_guard + 6 clean_sql + 12 escape_ident). - npx tsc --noEmit clean. - npx vitest run: 20 pass.
This commit is contained in:
168
src/lib/tauri.ts
168
src/lib/tauri.ts
@@ -1,5 +1,4 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import type {
|
||||
ConnectionConfig,
|
||||
ConnectResult,
|
||||
@@ -12,38 +11,13 @@ import type {
|
||||
ConstraintInfo,
|
||||
IndexInfo,
|
||||
TriggerInfo,
|
||||
ErdData,
|
||||
HistoryEntry,
|
||||
SavedQuery,
|
||||
SessionInfo,
|
||||
DatabaseInfo,
|
||||
CreateDatabaseParams,
|
||||
RoleInfo,
|
||||
CreateRoleParams,
|
||||
AlterRoleParams,
|
||||
TablePrivilege,
|
||||
GrantRevokeParams,
|
||||
RoleMembershipParams,
|
||||
AiSettings,
|
||||
OllamaModel,
|
||||
EntityLookupResult,
|
||||
LookupProgress,
|
||||
DockerStatus,
|
||||
CloneToDockerParams,
|
||||
CloneProgress,
|
||||
CloneResult,
|
||||
TuskContainer,
|
||||
AppSettings,
|
||||
McpStatus,
|
||||
ValidationRule,
|
||||
GenerateDataParams,
|
||||
GeneratedDataPreview,
|
||||
DataGenProgress,
|
||||
IndexAdvisorReport,
|
||||
SnapshotMetadata,
|
||||
CreateSnapshotParams,
|
||||
RestoreSnapshotParams,
|
||||
SnapshotProgress,
|
||||
ChatMessage,
|
||||
} from "@/types";
|
||||
|
||||
// Connections
|
||||
@@ -139,9 +113,6 @@ export const getTableTriggers = (
|
||||
table: string
|
||||
) => invoke<TriggerInfo[]>("get_table_triggers", { connectionId, schema, table });
|
||||
|
||||
export const getSchemaErd = (connectionId: string, schema: string) =>
|
||||
invoke<ErdData>("get_schema_erd", { connectionId, schema });
|
||||
|
||||
// Data
|
||||
export const getTableData = (params: {
|
||||
connectionId: string;
|
||||
@@ -229,47 +200,6 @@ export const exportJson = (
|
||||
rows: unknown[][]
|
||||
) => invoke<void>("export_json", { path, columns, rows });
|
||||
|
||||
// Management
|
||||
export const getDatabaseInfo = (connectionId: string) =>
|
||||
invoke<DatabaseInfo[]>("get_database_info", { connectionId });
|
||||
|
||||
export const createDatabase = (connectionId: string, params: CreateDatabaseParams) =>
|
||||
invoke<void>("create_database", { connectionId, params });
|
||||
|
||||
export const dropDatabase = (connectionId: string, name: string) =>
|
||||
invoke<void>("drop_database", { connectionId, name });
|
||||
|
||||
export const listRoles = (connectionId: string) =>
|
||||
invoke<RoleInfo[]>("list_roles", { connectionId });
|
||||
|
||||
export const createRole = (connectionId: string, params: CreateRoleParams) =>
|
||||
invoke<void>("create_role", { connectionId, params });
|
||||
|
||||
export const alterRole = (connectionId: string, params: AlterRoleParams) =>
|
||||
invoke<void>("alter_role", { connectionId, params });
|
||||
|
||||
export const dropRole = (connectionId: string, name: string) =>
|
||||
invoke<void>("drop_role", { connectionId, name });
|
||||
|
||||
export const getTablePrivileges = (connectionId: string, schema: string, table: string) =>
|
||||
invoke<TablePrivilege[]>("get_table_privileges", { connectionId, schema, table });
|
||||
|
||||
export const grantRevoke = (connectionId: string, params: GrantRevokeParams) =>
|
||||
invoke<void>("grant_revoke", { connectionId, params });
|
||||
|
||||
export const manageRoleMembership = (connectionId: string, params: RoleMembershipParams) =>
|
||||
invoke<void>("manage_role_membership", { connectionId, params });
|
||||
|
||||
// Sessions
|
||||
export const listSessions = (connectionId: string) =>
|
||||
invoke<SessionInfo[]>("list_sessions", { connectionId });
|
||||
|
||||
export const cancelQuery = (connectionId: string, pid: number) =>
|
||||
invoke<boolean>("cancel_query", { connectionId, pid });
|
||||
|
||||
export const terminateBackend = (connectionId: string, pid: number) =>
|
||||
invoke<boolean>("terminate_backend", { connectionId, pid });
|
||||
|
||||
// AI
|
||||
export const getAiSettings = () =>
|
||||
invoke<AiSettings>("get_ai_settings");
|
||||
@@ -289,50 +219,15 @@ export const explainSql = (connectionId: string, sql: string) =>
|
||||
export const fixSqlError = (connectionId: string, sql: string, errorMessage: string) =>
|
||||
invoke<string>("fix_sql_error", { connectionId, sql, errorMessage });
|
||||
|
||||
// Entity Lookup
|
||||
export const entityLookup = (
|
||||
config: ConnectionConfig,
|
||||
columnName: string,
|
||||
value: string,
|
||||
lookupId: string,
|
||||
databases?: string[]
|
||||
) =>
|
||||
invoke<EntityLookupResult>("entity_lookup", {
|
||||
config,
|
||||
columnName,
|
||||
value,
|
||||
lookupId,
|
||||
databases,
|
||||
});
|
||||
export const chatSend = (connectionId: string, messages: ChatMessage[]) =>
|
||||
invoke<ChatMessage[]>("chat_send", { connectionId, messages });
|
||||
|
||||
export const onLookupProgress = (
|
||||
callback: (p: LookupProgress) => void
|
||||
): Promise<UnlistenFn> =>
|
||||
listen<LookupProgress>("lookup-progress", (e) => callback(e.payload));
|
||||
// Memory (per-connection markdown notes for the chat agent)
|
||||
export const getMemory = (connectionId: string) =>
|
||||
invoke<string>("get_memory", { connectionId });
|
||||
|
||||
// Docker
|
||||
export const checkDocker = () =>
|
||||
invoke<DockerStatus>("check_docker");
|
||||
|
||||
export const listTuskContainers = () =>
|
||||
invoke<TuskContainer[]>("list_tusk_containers");
|
||||
|
||||
export const cloneToDocker = (params: CloneToDockerParams, cloneId: string) =>
|
||||
invoke<CloneResult>("clone_to_docker", { params, cloneId });
|
||||
|
||||
export const startContainer = (name: string) =>
|
||||
invoke<void>("start_container", { name });
|
||||
|
||||
export const stopContainer = (name: string) =>
|
||||
invoke<void>("stop_container", { name });
|
||||
|
||||
export const removeContainer = (name: string) =>
|
||||
invoke<void>("remove_container", { name });
|
||||
|
||||
export const onCloneProgress = (
|
||||
callback: (p: CloneProgress) => void
|
||||
): Promise<UnlistenFn> =>
|
||||
listen<CloneProgress>("clone-progress", (e) => callback(e.payload));
|
||||
export const saveMemory = (connectionId: string, content: string) =>
|
||||
invoke<void>("save_memory", { connectionId, content });
|
||||
|
||||
// App Settings
|
||||
export const getAppSettings = () =>
|
||||
@@ -343,50 +238,3 @@ export const saveAppSettings = (settings: AppSettings) =>
|
||||
|
||||
export const getMcpStatus = () =>
|
||||
invoke<McpStatus>("get_mcp_status");
|
||||
|
||||
// Validation (Wave 1)
|
||||
export const generateValidationSql = (connectionId: string, ruleDescription: string) =>
|
||||
invoke<string>("generate_validation_sql", { connectionId, ruleDescription });
|
||||
|
||||
export const runValidationRule = (connectionId: string, sql: string, sampleLimit?: number) =>
|
||||
invoke<ValidationRule>("run_validation_rule", { connectionId, sql, sampleLimit });
|
||||
|
||||
export const suggestValidationRules = (connectionId: string) =>
|
||||
invoke<string[]>("suggest_validation_rules", { connectionId });
|
||||
|
||||
// Data Generator (Wave 2)
|
||||
export const generateTestDataPreview = (params: GenerateDataParams, genId: string) =>
|
||||
invoke<GeneratedDataPreview>("generate_test_data_preview", { params, genId });
|
||||
|
||||
export const insertGeneratedData = (connectionId: string, preview: GeneratedDataPreview) =>
|
||||
invoke<number>("insert_generated_data", { connectionId, preview });
|
||||
|
||||
export const onDataGenProgress = (
|
||||
callback: (p: DataGenProgress) => void
|
||||
): Promise<UnlistenFn> =>
|
||||
listen<DataGenProgress>("datagen-progress", (e) => callback(e.payload));
|
||||
|
||||
// Index Advisor (Wave 3A)
|
||||
export const getIndexAdvisorReport = (connectionId: string) =>
|
||||
invoke<IndexAdvisorReport>("get_index_advisor_report", { connectionId });
|
||||
|
||||
export const applyIndexRecommendation = (connectionId: string, ddl: string) =>
|
||||
invoke<void>("apply_index_recommendation", { connectionId, ddl });
|
||||
|
||||
// Snapshots (Wave 3B)
|
||||
export const createSnapshot = (params: CreateSnapshotParams, snapshotId: string, filePath: string) =>
|
||||
invoke<SnapshotMetadata>("create_snapshot", { params, snapshotId, filePath });
|
||||
|
||||
export const restoreSnapshot = (params: RestoreSnapshotParams, snapshotId: string) =>
|
||||
invoke<number>("restore_snapshot", { params, snapshotId });
|
||||
|
||||
export const listSnapshots = () =>
|
||||
invoke<SnapshotMetadata[]>("list_snapshots");
|
||||
|
||||
export const readSnapshotMetadata = (filePath: string) =>
|
||||
invoke<SnapshotMetadata>("read_snapshot_metadata", { filePath });
|
||||
|
||||
export const onSnapshotProgress = (
|
||||
callback: (p: SnapshotProgress) => void
|
||||
): Promise<UnlistenFn> =>
|
||||
listen<SnapshotProgress>("snapshot-progress", (e) => callback(e.payload));
|
||||
|
||||
Reference in New Issue
Block a user