mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 18:39:54 +08:00
✨ feat(ai-tools): 新增最近SQL日志探针
- 新增 inspect_recent_sql_logs 内置工具用于回看最近 SQL 执行历史 - 接入本地工具执行链,支持按成功或失败状态筛选日志 - 更新 AI 设置内置工具目录、流程说明和工具状态文案 - 完成 vitest、生产构建与预览页内置工具目录验证
This commit is contained in:
@@ -1264,6 +1264,7 @@ SELECT * FROM users WHERE status = 1;
|
||||
connections: currentConnections,
|
||||
mcpTools,
|
||||
toolContextMap: toolContextMapRef.current,
|
||||
sqlLogs: useStore.getState().sqlLogs,
|
||||
});
|
||||
const toolResultMsg: AIChatMessage = buildToolResultMessage({
|
||||
id: genId(),
|
||||
|
||||
@@ -6,7 +6,7 @@ import AIBuiltinToolsCatalog from './AIBuiltinToolsCatalog';
|
||||
import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
|
||||
|
||||
describe('AIBuiltinToolsCatalog', () => {
|
||||
it('renders the field-to-table flow and both table-level and database-level snapshot tools', () => {
|
||||
it('renders the field-to-table flow, sql log replay flow, and both snapshot tools', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<AIBuiltinToolsCatalog
|
||||
darkMode={false}
|
||||
@@ -26,6 +26,8 @@ describe('AIBuiltinToolsCatalog', () => {
|
||||
expect(markup).toContain('inspect_table_bundle');
|
||||
expect(markup).toContain('全库快速摸底');
|
||||
expect(markup).toContain('inspect_database_bundle');
|
||||
expect(markup).toContain('回看最近执行记录');
|
||||
expect(markup).toContain('inspect_recent_sql_logs');
|
||||
expect(markup).toContain('理解样例数据');
|
||||
expect(markup).toContain('preview_table_rows');
|
||||
});
|
||||
|
||||
@@ -37,6 +37,11 @@ const BUILTIN_TOOL_FLOWS = [
|
||||
steps: 'inspect_database_bundle → inspect_table_bundle',
|
||||
description: '适合先看整库有哪些表、每张表大概有哪些字段,再对目标表继续做深挖快照。',
|
||||
},
|
||||
{
|
||||
title: '回看最近执行记录',
|
||||
steps: 'inspect_recent_sql_logs → get_columns / get_indexes / execute_sql',
|
||||
description: '适合追查刚刚执行失败的 SQL、慢查询耗时,或基于真实执行历史继续让 AI 给解释和优化建议。',
|
||||
},
|
||||
{
|
||||
title: '理解样例数据',
|
||||
steps: 'get_columns → preview_table_rows',
|
||||
|
||||
@@ -245,6 +245,58 @@ describe('aiLocalToolExecutor', () => {
|
||||
expect(result.content).toContain('"status":"paid"');
|
||||
});
|
||||
|
||||
it('returns recent sql logs and supports filtering only failed statements', async () => {
|
||||
const result = await executeLocalAIToolCall({
|
||||
toolCall: buildToolCall('inspect_recent_sql_logs', {
|
||||
limit: 2,
|
||||
status: 'error',
|
||||
}),
|
||||
connections: [buildConnection()],
|
||||
mcpTools: [],
|
||||
toolContextMap: new Map(),
|
||||
sqlLogs: [
|
||||
{
|
||||
id: 'log-1',
|
||||
timestamp: 3,
|
||||
sql: 'DELETE FROM users WHERE id = 9',
|
||||
status: 'error',
|
||||
duration: 120,
|
||||
message: 'permission denied',
|
||||
dbName: 'crm',
|
||||
},
|
||||
{
|
||||
id: 'log-2',
|
||||
timestamp: 2,
|
||||
sql: 'SELECT * FROM users LIMIT 10',
|
||||
status: 'success',
|
||||
duration: 18,
|
||||
dbName: 'crm',
|
||||
affectedRows: 10,
|
||||
},
|
||||
{
|
||||
id: 'log-3',
|
||||
timestamp: 1,
|
||||
sql: 'UPDATE orders SET status = \'paid\' WHERE id = 1',
|
||||
status: 'error',
|
||||
duration: 95,
|
||||
message: 'row lock timeout',
|
||||
dbName: 'crm',
|
||||
},
|
||||
],
|
||||
runtime: {
|
||||
getDatabases: vi.fn(),
|
||||
getTables: vi.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('"status":"error"');
|
||||
expect(result.content).toContain('"totalMatched":2');
|
||||
expect(result.content).toContain('permission denied');
|
||||
expect(result.content).toContain('row lock timeout');
|
||||
expect(result.content).not.toContain('SELECT * FROM users LIMIT 10');
|
||||
});
|
||||
|
||||
it('returns a database overview bundle with per-table column previews in one tool call', async () => {
|
||||
const result = await executeLocalAIToolCall({
|
||||
toolCall: buildToolCall('inspect_database_bundle', {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DBGetAllColumns, DBGetDatabases, DBGetTables } from '../../../wailsjs/go/app/App';
|
||||
|
||||
import type { SqlLog } from '../../store';
|
||||
import type { AIChatMessage, AIMCPToolDescriptor, AIToolCall, SavedConnection } from '../../types';
|
||||
import { buildRpcConnectionConfig } from '../../utils/connectionRpcConfig';
|
||||
import { buildAIReadonlyPreviewSQL } from '../../utils/aiSqlLimit';
|
||||
@@ -31,6 +32,7 @@ export interface ExecuteLocalAIToolCallOptions {
|
||||
connections: SavedConnection[];
|
||||
mcpTools: AIMCPToolDescriptor[];
|
||||
toolContextMap: Map<string, AIToolContextEntry>;
|
||||
sqlLogs?: SqlLog[];
|
||||
runtime?: Partial<AILocalToolRuntime>;
|
||||
}
|
||||
|
||||
@@ -137,6 +139,21 @@ const normalizePerTableColumnLimit = (input: unknown): number => {
|
||||
return value;
|
||||
};
|
||||
|
||||
const normalizeRecentSqlLogLimit = (input: unknown): number => {
|
||||
const value = Math.floor(Number(input) || 20);
|
||||
if (value < 1) return 1;
|
||||
if (value > 100) return 100;
|
||||
return value;
|
||||
};
|
||||
|
||||
const normalizeSqlLogStatus = (input: unknown): 'all' | 'success' | 'error' => {
|
||||
const value = String(input || 'all').trim().toLowerCase();
|
||||
if (value === 'success' || value === 'error') {
|
||||
return value;
|
||||
}
|
||||
return 'all';
|
||||
};
|
||||
|
||||
const buildPreviewSQLForTable = (connection: SavedConnection, tableName: string, limit: number): string => {
|
||||
const dbType = String(connection.config?.type || '').trim();
|
||||
return buildPaginatedSelectSQL(
|
||||
@@ -153,6 +170,7 @@ export async function executeLocalAIToolCall({
|
||||
connections,
|
||||
mcpTools,
|
||||
toolContextMap,
|
||||
sqlLogs = [],
|
||||
runtime,
|
||||
}: ExecuteLocalAIToolCallOptions): Promise<ExecuteLocalAIToolCallResult> {
|
||||
const mergedRuntime = { ...buildDefaultRuntime(), ...(runtime || {}) };
|
||||
@@ -556,6 +574,36 @@ export async function executeLocalAIToolCall({
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'inspect_recent_sql_logs': {
|
||||
try {
|
||||
const status = normalizeSqlLogStatus(args.status);
|
||||
const limit = normalizeRecentSqlLogLimit(args.limit);
|
||||
const filteredLogs = sqlLogs.filter((log) => status === 'all' || log.status === status);
|
||||
const visibleLogs = filteredLogs.slice(0, limit).map((log) => ({
|
||||
id: log.id,
|
||||
timestamp: log.timestamp,
|
||||
status: log.status,
|
||||
duration: log.duration,
|
||||
dbName: log.dbName || '',
|
||||
affectedRows: typeof log.affectedRows === 'number' ? log.affectedRows : null,
|
||||
sql: log.sql,
|
||||
message: log.message || '',
|
||||
}));
|
||||
|
||||
content = JSON.stringify({
|
||||
status,
|
||||
limit,
|
||||
totalMatched: filteredLogs.length,
|
||||
successCount: filteredLogs.filter((log) => log.status === 'success').length,
|
||||
errorCount: filteredLogs.filter((log) => log.status === 'error').length,
|
||||
logs: visibleLogs,
|
||||
});
|
||||
success = true;
|
||||
} catch (error: any) {
|
||||
content = `获取最近 SQL 日志失败: ${error?.message || error}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'preview_table_rows': {
|
||||
const connection = findConnection(connections, args.connectionId);
|
||||
if (!connection) {
|
||||
|
||||
@@ -34,6 +34,7 @@ const TOOL_ACTION_LABELS: Record<string, string> = {
|
||||
get_table_ddl: '提取建表语句',
|
||||
inspect_table_bundle: '抓取完整表结构快照',
|
||||
inspect_database_bundle: '抓取数据库结构总览',
|
||||
inspect_recent_sql_logs: '回看最近 SQL 执行日志',
|
||||
preview_table_rows: '预览真实样例数据',
|
||||
execute_sql: '执行只读 SQL 验证',
|
||||
};
|
||||
|
||||
@@ -309,6 +309,33 @@ export const BUILTIN_AI_TOOL_INFO: AIBuiltinToolInfo[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inspect_recent_sql_logs",
|
||||
icon: "🧾",
|
||||
desc: "查看最近 SQL 执行日志",
|
||||
detail:
|
||||
"传入可选 limit 和 status,返回最近 SQL 执行记录,包括数据库、耗时、成功/失败、报错、受影响行数和 SQL 文本。适合追查刚执行失败的语句、定位慢查询,并让 AI 基于真实执行历史给出解释或优化建议。",
|
||||
params: "limit?, status?(all|success|error)",
|
||||
tool: {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "inspect_recent_sql_logs",
|
||||
description:
|
||||
"获取最近 SQL 执行日志摘要,可按成功/失败过滤。适用于回看刚执行过的 SQL、排查失败原因、定位慢查询,以及让 AI 基于真实执行历史给出解释和优化建议。",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: "number", description: "可选,返回多少条日志,默认 20,最大 100" },
|
||||
status: {
|
||||
type: "string",
|
||||
description: "可选,按执行状态过滤,支持 all、success、error,默认 all",
|
||||
enum: ["all", "success", "error"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "execute_sql",
|
||||
icon: "▶️",
|
||||
|
||||
Reference in New Issue
Block a user