Tong G 3 周之前
父节点
当前提交
1d9def2ee8

二进制
DBconfig/storage.db


+ 245 - 0
app.log

@@ -1589,3 +1589,248 @@
 {"level":"info","timestamp":"2025-12-11T17:28:07.353+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
 {"level":"info","timestamp":"2025-12-11T17:28:07.353+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
 {"level":"info","timestamp":"2025-12-11T17:28:07.353+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.625+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"AI 交互未启用,跳过 MCP ai_chat 注册"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T10:41:12.626+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"error","timestamp":"2025-12-17T11:16:29.358+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-17T11:19:12.563+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"warn","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:109","message":"AI Provider 未支持,跳过 MCP ai_chat 注册","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T11:19:12.564+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"error","timestamp":"2025-12-17T16:31:12.156+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-17T16:39:17.473+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"warn","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:109","message":"读取 AI 配置失败,跳过 MCP ai_chat 注册","error":"读取 ai_config 失败: SQL logic error: no such table: ai_config (1)"}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:39:17.477+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-17T16:39:25.248+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"warn","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:109","message":"读取 AI 配置失败,跳过 MCP ai_chat 注册","error":"读取 ai_config 失败: SQL logic error: no such table: ai_config (1)"}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:39:25.249+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.973+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"warn","timestamp":"2025-12-17T16:42:45.974+0800","caller":"logger/logger.go:109","message":"读取 AI 配置失败,跳过 MCP ai_chat 注册","error":"读取 ai_config 失败: SQL logic error: no such table: ai_config (1)"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.974+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.974+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:42:45.974+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-17T16:42:45.975+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-17T16:42:47.011+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"info","timestamp":"2025-12-17T16:42:47.011+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"error","timestamp":"2025-12-17T16:42:47.011+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-17T16:42:47.022+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.125+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.127+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-17T17:14:38.128+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T09:23:46.116+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T09:23:46.116+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-18T09:23:46.126+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"warn","timestamp":"2025-12-18T09:23:46.127+0800","caller":"logger/logger.go:109","message":"关闭连接池失败","error":"关闭连接 ac99acd1@127-0-0-1 失败: bad connection"}
+{"level":"info","timestamp":"2025-12-18T09:23:46.133+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.162+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.165+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.165+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.165+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T09:23:50.166+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T09:27:03.754+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T09:27:03.754+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-18T09:27:03.763+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T09:27:03.767+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.327+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.329+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T09:27:07.330+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T13:59:06.037+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T13:59:06.037+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:53"}
+{"level":"info","timestamp":"2025-12-18T13:59:06.044+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T13:59:06.049+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.697+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.700+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T15:00:57.701+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T15:13:06.312+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T15:13:06.312+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-18T15:13:06.318+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T15:13:06.323+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.572+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.575+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.575+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.575+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:16:43.575+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.575+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.576+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T15:16:43.576+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:16:43.576+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.576+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T15:16:43.576+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T15:51:10.040+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T15:51:10.040+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-18T15:51:10.049+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T15:51:10.053+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.552+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.554+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.554+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.554+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:53:09.554+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T15:53:09.555+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T16:06:07.910+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T16:06:07.910+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-18T16:06:07.916+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T16:06:07.920+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.609+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.611+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.611+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.611+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T16:06:11.611+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T16:06:11.612+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T16:23:20.852+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T16:23:20.852+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-18T16:23:20.860+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T16:23:20.865+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.942+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.944+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T16:23:24.945+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-18T17:51:50.877+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-18T17:51:50.878+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-18T17:51:50.886+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-18T17:51:50.890+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.714+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-18T17:51:54.716+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-19T16:07:15.626+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-19T16:07:15.626+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-19T16:07:15.635+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-19T16:07:15.643+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.153+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.155+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.155+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.155+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:07:19.155+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-19T16:07:19.156+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-19T16:32:22.854+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-19T16:32:22.854+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-19T16:32:22.861+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-19T16:32:22.867+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.204+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-19T16:32:26.206+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-19T16:35:35.948+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"error","timestamp":"2025-12-19T16:35:35.948+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-19T16:35:35.957+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"info","timestamp":"2025-12-19T16:35:35.960+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.786+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.788+0800","caller":"logger/logger.go:104","message":"MCP ai_chat 工具已注册","model":"deepseek-chat'","provider":"deepseek"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.788+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.788+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:35:37.788+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-19T16:35:37.789+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-12-19T17:30:41.677+0800","caller":"logger/logger.go:104","message":"收到信号,开始关闭","signal":"interrupt"}
+{"level":"info","timestamp":"2025-12-19T17:30:41.678+0800","caller":"logger/logger.go:104","message":"开始关闭应用"}
+{"level":"error","timestamp":"2025-12-19T17:30:41.677+0800","caller":"logger/logger.go:114","message":"MCP ServeStdio 启动失败","error":"context canceled","stacktrace":"dbview/service/internal/common/logger.(*zapLogger).Error\n\t/Volumes/Run Barry/code/db_view/service/internal/common/logger/logger.go:114\ndbview/service/internal/bootstrap.initializeMCP.func1\n\t/Volumes/Run Barry/code/db_view/service/internal/bootstrap/mcp.go:52"}
+{"level":"info","timestamp":"2025-12-19T17:30:41.691+0800","caller":"logger/logger.go:104","message":"应用已关闭"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.128+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"warn","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:109","message":"AI 交互已启用但未配置 api_key"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"MCP 组件已启用","server_name":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db","mcp_enabled":true}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"启动 MCP Stdio 服务","server":"dbview-mcp","version":"0.1.0"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"静态文件挂载已禁用(运行时参数控制)","mount_flag":false}
+{"level":"info","timestamp":"2025-12-19T17:30:45.135+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-12-19T17:30:45.136+0800","caller":"logger/logger.go:104","message":"DB View 应用启动成功"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.136+0800","caller":"logger/logger.go:104","message":"应用开始运行"}
+{"level":"info","timestamp":"2025-12-19T17:30:45.136+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}

+ 6 - 0
config.toml

@@ -21,3 +21,9 @@ config_path = './DBconfig/data.toml'
 sql_base_dir = './DBconfig/sqlfiles'
 database_path = './DBconfig/storage.db'
 data_file = './DBconfig/data.toml'
+
+[mcp]
+enable = true  # 仅启用 MCP 时会自动注册 ai_chat 工具;HTTP 接口与 MCP 各自独立
+
+[ai]
+enable = true

+ 34 - 22
service/internal/bootstrap/mcp.go

@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
-	"os"
 	"time"
 
 	"dbview/service/internal/common/databases/meta"
@@ -46,7 +45,7 @@ func initializeMCP(ctx context.Context, cfg *config.AppConfig, log logger.Logger
 	// 与当前业务相关的工具
 	registerConnectionTools(comp, storageMgr, log)
 	registerExecuteSQLTool(comp, pool, taskMgr, log)
-	registerAIChatTool(comp, cfg.AI, log)
+	registerAIChatTool(comp, cfg.AI, storageMgr, log)
 
 	go func() {
 		if err := comp.ServeStdio(ctx); err != nil && log != nil {
@@ -143,7 +142,7 @@ func registerExecuteSQLTool(comp *mcp.Component, pool *connection.ConnectionPool
 }
 
 // registerAIChatTool exposes a simple AI chat tool backed by OpenAI HTTP API.
-func registerAIChatTool(comp *mcp.Component, aiCfg config.AIConfig, log logger.Logger) {
+func registerAIChatTool(comp *mcp.Component, aiCfg config.AIConfig, storageMgr storage.StorageInterface, log logger.Logger) {
 	if !aiCfg.Enable {
 		if log != nil {
 			log.Info("AI 交互未启用,跳过 MCP ai_chat 注册")
@@ -151,32 +150,42 @@ func registerAIChatTool(comp *mcp.Component, aiCfg config.AIConfig, log logger.L
 		return
 	}
 
-	if aiCfg.Provider != "openai" {
+	if storageMgr == nil {
 		if log != nil {
-			log.Warn("AI Provider 未支持,跳过 MCP ai_chat 注册", zap.String("provider", aiCfg.Provider))
+			log.Warn("AI 交互已启用但存储未初始化,跳过 MCP ai_chat 注册")
 		}
 		return
 	}
 
-	apiKeyEnv := aiCfg.APIKeyEnv
-	if apiKeyEnv == "" {
-		apiKeyEnv = "OPENAI_API_KEY"
-	}
-	apiKey := os.Getenv(apiKeyEnv)
-	if apiKey == "" {
+	aiSettings, err := storageMgr.GetAIConfig()
+	if err != nil {
 		if log != nil {
-			log.Warn("AI 交互已启用但未找到 API Key 环境变量", zap.String("env", apiKeyEnv))
+			log.Warn("读取 AI 配置失败,跳过 MCP ai_chat 注册", zap.Error(err))
 		}
 		return
 	}
 
-	defaultModel := aiCfg.Model
+	provider := aiSettings.Provider
+	if provider == "" {
+		provider = "openai"
+	}
+	baseURL := aiSettings.BaseURL
+	if baseURL == "" {
+		baseURL = "https://api.openai.com/v1/chat/completions"
+	}
+	defaultModel := aiSettings.Model
 	if defaultModel == "" {
 		defaultModel = "gpt-4o-mini"
 	}
+	if aiSettings.APIKey == "" {
+		if log != nil {
+			log.Warn("AI 交互已启用但未配置 api_key")
+		}
+		return
+	}
 
 	tool := mcpgo.NewTool("ai_chat",
-		mcpgo.WithDescription("调用外部 AI 模型生成回复 (OpenAI)"),
+		mcpgo.WithDescription("调用外部 AI 模型生成回复"),
 		mcpgo.WithString("prompt", mcpgo.Description("用户提示,必填")),
 		mcpgo.WithString("system", mcpgo.Description("系统指令,可选")),
 		mcpgo.WithString("model", mcpgo.Description("覆盖默认模型,可选")),
@@ -193,11 +202,14 @@ func registerAIChatTool(comp *mcp.Component, aiCfg config.AIConfig, log logger.L
 		if override, ok := args["model"].(string); ok && override != "" {
 			model = override
 		}
+		if p, ok := args["provider"].(string); ok && p != "" && p != provider {
+			return nil, fmt.Errorf("不支持请求覆盖 provider")
+		}
 
 		callCtx, cancel := context.WithTimeout(ctx, 45*time.Second)
 		defer cancel()
 
-		respText, err := callOpenAIChat(callCtx, apiKey, model, systemPrompt, prompt)
+		respText, err := callOpenAICompatible(callCtx, baseURL, aiSettings.APIKey, model, systemPrompt, prompt, provider)
 		if err != nil {
 			return nil, err
 		}
@@ -206,12 +218,12 @@ func registerAIChatTool(comp *mcp.Component, aiCfg config.AIConfig, log logger.L
 	})
 
 	if log != nil {
-		log.Info("MCP ai_chat 工具已注册", zap.String("model", defaultModel), zap.String("provider", aiCfg.Provider))
+		log.Info("MCP ai_chat 工具已注册", zap.String("model", defaultModel), zap.String("provider", provider))
 	}
 }
 
-// callOpenAIChat performs a minimal chat completion request.
-func callOpenAIChat(ctx context.Context, apiKey, model, systemPrompt, userPrompt string) (string, error) {
+// callOpenAICompatible performs a chat completion request against an OpenAI-compatible endpoint.
+func callOpenAICompatible(ctx context.Context, baseURL, apiKey, model, systemPrompt, userPrompt, provider string) (string, error) {
 	if apiKey == "" {
 		return "", fmt.Errorf("missing api key")
 	}
@@ -228,7 +240,7 @@ func callOpenAIChat(ctx context.Context, apiKey, model, systemPrompt, userPrompt
 		return "", err
 	}
 
-	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/chat/completions", bytes.NewReader(b))
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(b))
 	if err != nil {
 		return "", err
 	}
@@ -248,13 +260,13 @@ func callOpenAIChat(ctx context.Context, apiKey, model, systemPrompt, userPrompt
 
 	if resp.StatusCode >= 300 {
 		if out.Error != nil && out.Error.Message != "" {
-			return "", fmt.Errorf("openai error: %s", out.Error.Message)
+			return "", fmt.Errorf("%s error: %s", provider, out.Error.Message)
 		}
-		return "", fmt.Errorf("openai request failed: status %d", resp.StatusCode)
+		return "", fmt.Errorf("%s request failed: status %d", provider, resp.StatusCode)
 	}
 
 	if len(out.Choices) == 0 {
-		return "", fmt.Errorf("openai 返回为空")
+		return "", fmt.Errorf("%s 返回为空", provider)
 	}
 
 	return out.Choices[0].Message.Content, nil

+ 325 - 0
service/internal/common/manager/storage/db_storage/ai_storage.go

@@ -0,0 +1,325 @@
+package db_storage
+
+import (
+	"database/sql"
+	"fmt"
+	"strings"
+	"time"
+
+	"dbview/service/internal/common/manager/storage/types"
+
+	"github.com/google/uuid"
+)
+
+// GetAIConfig 从存储读取 AI 配置;如果缺少字段会按默认填充。
+func (sm *StorageManager) GetAIConfig() (*types.AISettings, error) {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	var cfg types.AISettings
+	var updated sql.NullTime
+	// 新列:auth_header, auth_scheme, extra_headers, request_type
+	err := sm.db.QueryRow(`SELECT id, name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at FROM ai_config WHERE id = ?`, "default").Scan(&cfg.ID, &cfg.Name, &cfg.Provider, &cfg.Model, &cfg.BaseURL, &cfg.APIKey, &cfg.AuthHeader, &cfg.AuthScheme, &cfg.ExtraHeaders, &cfg.RequestType, &updated)
+	if err != nil {
+		if err == sql.ErrNoRows {
+			cfg = defaultAISettings()
+			if _, insErr := sm.db.Exec(`INSERT OR REPLACE INTO ai_config (id, name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`, "default", cfg.Name, cfg.Provider, cfg.Model, cfg.BaseURL, cfg.APIKey, cfg.AuthHeader, cfg.AuthScheme, cfg.ExtraHeaders, cfg.RequestType); insErr != nil {
+				return nil, fmt.Errorf("写入默认 ai_config 失败: %v", insErr)
+			}
+			return &cfg, nil
+		}
+		return nil, fmt.Errorf("读取 ai_config 失败: %v", err)
+	}
+	if updated.Valid {
+		cfg.UpdatedAt = updated.Time
+	}
+
+	if cfg.Provider == "" {
+		cfg.Provider = "openai"
+	}
+	if cfg.Model == "" {
+		cfg.Model = "gpt-4o-mini"
+	}
+	if cfg.BaseURL == "" {
+		cfg.BaseURL = "https://api.openai.com/v1/chat/completions"
+	}
+
+	return &cfg, nil
+}
+
+// UpdateAIConfig 覆盖存储中的 AI 配置(默认 id=default,允许传入其他 id)。
+func (sm *StorageManager) UpdateAIConfig(cfg types.AISettings) (*types.AISettings, error) {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	targetID := strings.TrimSpace(cfg.ID)
+	if targetID == "" {
+		targetID = "default"
+	}
+
+	// 读取当前配置以便部分字段留空时保留原值
+	current := defaultAISettings()
+	current.ID = targetID
+	var updated sql.NullTime
+	if err := sm.db.QueryRow(`SELECT name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at FROM ai_config WHERE id = ?`, targetID).Scan(&current.Name, &current.Provider, &current.Model, &current.BaseURL, &current.APIKey, &current.AuthHeader, &current.AuthScheme, &current.ExtraHeaders, &current.RequestType, &updated); err != nil && err != sql.ErrNoRows {
+		return nil, fmt.Errorf("读取当前 ai_config 失败: %v", err)
+	}
+	if updated.Valid {
+		current.UpdatedAt = updated.Time
+	}
+
+	final := current
+	if cfg.Name != "" {
+		final.Name = cfg.Name
+	}
+	if cfg.Provider != "" {
+		final.Provider = cfg.Provider
+	}
+	if cfg.Model != "" {
+		final.Model = cfg.Model
+	}
+	if cfg.BaseURL != "" {
+		final.BaseURL = cfg.BaseURL
+	}
+	if cfg.APIKey != "" {
+		final.APIKey = cfg.APIKey
+	}
+
+	if final.Provider == "" {
+		final.Provider = "openai"
+	}
+	if final.Model == "" {
+		final.Model = "gpt-4o-mini"
+	}
+	if final.BaseURL == "" {
+		final.BaseURL = "https://api.openai.com/v1/chat/completions"
+	}
+	// api_key 允许为空(用户可仅修改部分字段)
+
+	if final.Name == "" {
+		final.Name = "default"
+	}
+	final.ID = targetID
+
+	_, err := sm.db.Exec(`INSERT OR REPLACE INTO ai_config (id, name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+		targetID, final.Name, final.Provider, final.Model, final.BaseURL, final.APIKey, final.AuthHeader, final.AuthScheme, final.ExtraHeaders, final.RequestType)
+	if err != nil {
+		return nil, fmt.Errorf("更新 ai_config 失败: %v", err)
+	}
+
+	return &final, nil
+}
+
+// CreateAIConfig 新建 AI 配置(单行表 upsert)。
+func (sm *StorageManager) CreateAIConfig(cfg types.AISettings) (*types.AISettings, error) {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	final := cfg
+	final.ID = strings.TrimSpace(final.ID)
+	final.Name = strings.TrimSpace(final.Name)
+	// 前端无需传 ID:如果为空则由后端生成短 UUID,避免覆盖已有记录。
+	if final.ID == "" {
+		final.ID = generateID()
+	}
+
+	if final.Name == "" {
+		return nil, fmt.Errorf("ai_config 名称不能为空")
+	}
+
+	// 若已存在相同 ID 的配置,则返回错误,避免覆盖。
+	var exists int
+	if err := sm.db.QueryRow(`SELECT COUNT(1) FROM ai_config WHERE id = ?`, final.ID).Scan(&exists); err != nil {
+		return nil, fmt.Errorf("检查 ai_config 是否存在失败: %v", err)
+	}
+	if exists > 0 {
+		return nil, fmt.Errorf("ai_config 已存在: %s", final.ID)
+	}
+
+	// 名称需唯一,防止创建同名配置。
+	var nameExists int
+	if err := sm.db.QueryRow(`SELECT COUNT(1) FROM ai_config WHERE name = ?`, final.Name).Scan(&nameExists); err != nil {
+		return nil, fmt.Errorf("检查 ai_config 名称是否存在失败: %v", err)
+	}
+	if nameExists > 0 {
+		return nil, fmt.Errorf("ai_config 名称已存在: %s", final.Name)
+	}
+
+	if final.Provider == "" {
+		final.Provider = "openai"
+	}
+	if final.Model == "" {
+		final.Model = "gpt-4o-mini"
+	}
+	if final.BaseURL == "" {
+		final.BaseURL = "https://api.openai.com/v1/chat/completions"
+	}
+	if final.AuthHeader == "" {
+		final.AuthHeader = "Authorization"
+	}
+	if final.AuthScheme == "" {
+		final.AuthScheme = "Bearer"
+	}
+	if final.RequestType == "" {
+		final.RequestType = "openai"
+	}
+	// api_key 可为空
+
+	_, err := sm.db.Exec(`INSERT OR REPLACE INTO ai_config (id, name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+		final.ID, final.Name, final.Provider, final.Model, final.BaseURL, final.APIKey, final.AuthHeader, final.AuthScheme, final.ExtraHeaders, final.RequestType)
+	if err != nil {
+		return nil, fmt.Errorf("创建 ai_config 失败: %v", err)
+	}
+
+	return &final, nil
+}
+
+// SaveAIMessage 追加一条对话消息到历史表。
+// 要求:session_id、role、content 均非空;若未提供 ID 则自动生成 8 字节短 UUID;created_at 由服务端写入。
+// 说明:该方法仅负责持久化,不做幂等检查,调用方应按会话顺序写入 user/assistant。
+func (sm *StorageManager) SaveAIMessage(msg *types.AIMessage) error {
+	if msg == nil {
+		return fmt.Errorf("message 不能为空")
+	}
+	if msg.SessionID == "" {
+		return fmt.Errorf("session_id 不能为空")
+	}
+	if msg.Role == "" {
+		return fmt.Errorf("role 不能为空")
+	}
+	if msg.Content == "" {
+		return fmt.Errorf("content 不能为空")
+	}
+
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	id := msg.ID
+	if id == "" {
+		id = uuid.New().String()[:8]
+	}
+	createdAt := time.Now()
+
+	_, err := sm.db.Exec(`INSERT INTO ai_chat_history (id, session_id, role, content, model, provider, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
+		id, msg.SessionID, msg.Role, msg.Content, msg.Model, msg.Provider, createdAt)
+	if err != nil {
+		return fmt.Errorf("写入 ai_chat_history 失败: %v", err)
+	}
+
+	msg.ID = id
+	msg.CreatedAt = createdAt
+	return nil
+}
+
+// ListAIHistory 按 session_id 查询消息,时间正序。
+// limit 默认 100,offset 默认 0;用于前端分页读取会话历史。
+func (sm *StorageManager) ListAIHistory(sessionID string, limit, offset int) ([]types.AIMessage, error) {
+	if sessionID == "" {
+		return nil, fmt.Errorf("session_id 不能为空")
+	}
+	if limit <= 0 {
+		limit = 100
+	}
+	if offset < 0 {
+		offset = 0
+	}
+
+	sm.mu.RLock()
+	defer sm.mu.RUnlock()
+
+	rows, err := sm.db.Query(`SELECT id, session_id, role, content, model, provider, created_at FROM ai_chat_history WHERE session_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?`, sessionID, limit, offset)
+	if err != nil {
+		return nil, fmt.Errorf("查询 ai_chat_history 失败: %v", err)
+	}
+	defer rows.Close()
+
+	var messages []types.AIMessage
+	for rows.Next() {
+		var m types.AIMessage
+		if err := rows.Scan(&m.ID, &m.SessionID, &m.Role, &m.Content, &m.Model, &m.Provider, &m.CreatedAt); err != nil {
+			return nil, fmt.Errorf("解析 ai_chat_history 失败: %v", err)
+		}
+		messages = append(messages, m)
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, fmt.Errorf("遍历 ai_chat_history 失败: %v", err)
+	}
+
+	return messages, nil
+}
+
+// DeleteAIHistory 删除指定 session 的所有历史记录。
+// 用途:前端“清空聊天”时一次性移除某会话的全部消息。
+func (sm *StorageManager) DeleteAIHistory(sessionID string) error {
+	if sessionID == "" {
+		return fmt.Errorf("session_id 不能为空")
+	}
+
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	if _, err := sm.db.Exec(`DELETE FROM ai_chat_history WHERE session_id = ?`, sessionID); err != nil {
+		return fmt.Errorf("删除 ai_chat_history 失败: %v", err)
+	}
+
+	return nil
+}
+
+// DeleteAIConfig 删除指定 id 的 AI 配置(单行表通常为 id=default)。
+func (sm *StorageManager) DeleteAIConfig(id string) error {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	if id == "" {
+		id = "default"
+	}
+
+	if _, err := sm.db.Exec(`DELETE FROM ai_config WHERE id = ?`, id); err != nil {
+		return fmt.Errorf("删除 ai_config 失败: %v", err)
+	}
+
+	return nil
+}
+
+// ListAIConfigs 返回全部 AI 配置记录,按更新时间倒序。
+func (sm *StorageManager) ListAIConfigs() ([]types.AISettings, error) {
+	sm.mu.RLock()
+	defer sm.mu.RUnlock()
+
+	rows, err := sm.db.Query(`SELECT id, name, provider, model, base_url, api_key, auth_header, auth_scheme, extra_headers, request_type, updated_at FROM ai_config ORDER BY updated_at DESC`)
+	if err != nil {
+		return nil, fmt.Errorf("查询 ai_config 失败: %v", err)
+	}
+	defer rows.Close()
+
+	var res []types.AISettings
+	for rows.Next() {
+		var cfg types.AISettings
+		var updated sql.NullTime
+		if err := rows.Scan(&cfg.ID, &cfg.Name, &cfg.Provider, &cfg.Model, &cfg.BaseURL, &cfg.APIKey, &cfg.AuthHeader, &cfg.AuthScheme, &cfg.ExtraHeaders, &cfg.RequestType, &updated); err != nil {
+			return nil, fmt.Errorf("解析 ai_config 失败: %v", err)
+		}
+		if updated.Valid {
+			cfg.UpdatedAt = updated.Time
+		}
+		res = append(res, cfg)
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, fmt.Errorf("遍历 ai_config 失败: %v", err)
+	}
+
+	return res, nil
+}
+
+func defaultAISettings() types.AISettings {
+	return types.AISettings{
+		ID:       "default",
+		Name:     "default",
+		Provider: "openai",
+		Model:    "gpt-4o-mini",
+		BaseURL:  "https://api.openai.com/v1/chat/completions",
+		APIKey:   "",
+	}
+}

+ 7 - 0
service/internal/common/manager/storage/db_storage/connection_operations.go

@@ -354,5 +354,12 @@ func (sm *StorageManager) CreateDefaultConfig() error {
 		return fmt.Errorf("创建根脚本分组失败,错误: %v", err)
 	}
 
+	// 写入默认 AI 配置(单行表 ai_config)
+	defaultAI := defaultAISettings()
+	_, err = sm.db.Exec(`INSERT OR IGNORE INTO ai_config (id, name, provider, model, base_url, api_key) VALUES (?, ?, ?, ?, ?, ?)`, "default", defaultAI.Name, defaultAI.Provider, defaultAI.Model, defaultAI.BaseURL, defaultAI.APIKey)
+	if err != nil {
+		return fmt.Errorf("写入默认 AI 配置失败: %v", err)
+	}
+
 	return nil
 }

+ 27 - 6
service/internal/common/manager/storage/db_storage/manager.go

@@ -171,14 +171,35 @@ FOREIGN KEY (connection_id) REFERENCES connections(id) ON DELETE SET NULL,
 FOREIGN KEY (group_id) REFERENCES script_groups(id) ON DELETE SET NULL
 );`
 
-	// 创建配置表
-	configSQL := `
-CREATE TABLE IF NOT EXISTS config (
-key TEXT PRIMARY KEY,
-value TEXT
+	aiConfigSQL := `
+CREATE TABLE IF NOT EXISTS ai_config (
+id TEXT PRIMARY KEY,
+	name TEXT NOT NULL,
+provider TEXT NOT NULL,
+model TEXT NOT NULL,
+base_url TEXT NOT NULL,
+api_key TEXT NOT NULL,
+auth_header TEXT,
+auth_scheme TEXT,
+extra_headers TEXT,
+request_type TEXT,
+updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );`
 
-	tables := []string{connectionGroupSQL, connectionSQL, dbConnSQL, serverConnSQL, scriptGroupSQL, scriptsSQL, configSQL}
+	aiChatHistorySQL := `
+CREATE TABLE IF NOT EXISTS ai_chat_history (
+id TEXT PRIMARY KEY,
+session_id TEXT NOT NULL,
+role TEXT NOT NULL,
+content TEXT NOT NULL,
+model TEXT,
+provider TEXT,
+created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+CREATE INDEX IF NOT EXISTS idx_ai_chat_history_session_created ON ai_chat_history(session_id, created_at);
+`
+
+	tables := []string{connectionGroupSQL, connectionSQL, dbConnSQL, serverConnSQL, scriptGroupSQL, scriptsSQL, aiConfigSQL, aiChatHistorySQL}
 
 	for i, sql := range tables {
 		if _, err := sm.db.Exec(sql); err != nil {

+ 8 - 0
service/internal/common/manager/storage/interface.go

@@ -44,6 +44,14 @@ type StorageInterface interface {
 
 	// 配置管理
 	CreateDefaultConfig() error
+	CreateAIConfig(cfg types.AISettings) (*types.AISettings, error)
+	GetAIConfig() (*types.AISettings, error)
+	UpdateAIConfig(cfg types.AISettings) (*types.AISettings, error)
+	DeleteAIConfig(id string) error
+	SaveAIMessage(msg *types.AIMessage) error
+	ListAIHistory(sessionID string, limit, offset int) ([]types.AIMessage, error)
+	DeleteAIHistory(sessionID string) error
+	ListAIConfigs() ([]types.AISettings, error)
 }
 
 // 类型别名:当前保留以减少迁移成本,但新代码应直接引用 `types` 包中的类型。

+ 27 - 0
service/internal/common/manager/storage/types/types.go

@@ -130,3 +130,30 @@ type UpdateConnectionRequest struct {
 	AutoConnect  *bool   `json:"auto_connect,omitempty"`
 	DisplayOrder *int    `json:"display_order,omitempty"`
 }
+
+// AISettings 存储中的 AI 配置(OpenAI 兼容)
+type AISettings struct {
+	ID       string `json:"id,omitempty"`
+	Name     string `json:"name"`
+	Provider string `json:"provider"`
+	Model    string `json:"model"`
+	BaseURL  string `json:"base_url"`
+	APIKey   string `json:"api_key"`
+	// 可选的认证配置:
+	AuthHeader   string    `json:"auth_header,omitempty"`   // header 名称,例如 "Authorization" 或 "x-api-key"
+	AuthScheme   string    `json:"auth_scheme,omitempty"`   // scheme,例如 "Bearer" 或 空字符串(直接使用 key)
+	ExtraHeaders string    `json:"extra_headers,omitempty"` // JSON 字符串表示的额外 headers map
+	RequestType  string    `json:"request_type,omitempty"`  // provider 请求类型,例如 "openai" 或 "mimo"
+	UpdatedAt    time.Time `json:"updated_at,omitempty"`
+}
+
+// AIMessage 表示一次对话消息(存储于 ai_chat_history)。
+type AIMessage struct {
+	ID        string    `json:"id"`
+	SessionID string    `json:"session_id"`
+	Role      string    `json:"role"` // system/user/assistant
+	Content   string    `json:"content"`
+	Model     string    `json:"model,omitempty"`
+	Provider  string    `json:"provider,omitempty"`
+	CreatedAt time.Time `json:"created_at"`
+}

+ 1 - 4
service/internal/config/config.example.toml

@@ -46,7 +46,4 @@ server_version = "0.1.0"
 
 [ai]
 enable = false
-provider = "openai"
-model = "gpt-4o-mini"
-# 从该环境变量读取 API Key
-api_key_env = "OPENAI_API_KEY"
+# provider/model/base_url/api_key 由存储中的 ai_config 管理,可通过 /ai/config 接口查看或更新

+ 4 - 30
service/internal/config/config.go

@@ -67,10 +67,7 @@ type MCPConfig struct {
 
 // AIConfig AI 交互配置
 type AIConfig struct {
-	Enable    bool   `toml:"enable"`
-	Provider  string `toml:"provider"`    // 目前支持 openai
-	Model     string `toml:"model"`       // 默认模型
-	APIKeyEnv string `toml:"api_key_env"` // 从哪个环境变量读取 API Key
+	Enable bool `toml:"enable"`
 }
 
 // LoadConfig 加载配置文件
@@ -131,10 +128,7 @@ func CreateDefaultConfig(configPath string) error {
 			ServerVersion: "0.1.0",
 		},
 		AI: AIConfig{
-			Enable:    false,
-			Provider:  "openai",
-			Model:     "gpt-4o-mini",
-			APIKeyEnv: "OPENAI_API_KEY",
+			Enable: false,
 		},
 	}
 
@@ -231,15 +225,7 @@ func (cfg *AppConfig) SetDefaults() {
 	}
 
 	// AI 默认值
-	if cfg.AI.Provider == "" {
-		cfg.AI.Provider = "openai"
-	}
-	if cfg.AI.Model == "" {
-		cfg.AI.Model = "gpt-4o-mini"
-	}
-	if cfg.AI.APIKeyEnv == "" {
-		cfg.AI.APIKeyEnv = "OPENAI_API_KEY"
-	}
+	// 仅启用开关由 TOML 决定,其他 AI 配置改由存储加载
 }
 
 // GetLogLevel 将字符串日志级别转换为 zapcore.Level
@@ -322,19 +308,7 @@ func (cfg *AppConfig) Validate() error {
 		return fmt.Errorf("invalid storage type: %s", cfg.Storage.Type)
 	}
 
-	// 验证 AI 配置(仅在启用时检查)
-	if cfg.AI.Enable {
-		validProviders := map[string]bool{"openai": true}
-		if !validProviders[cfg.AI.Provider] {
-			return fmt.Errorf("invalid ai provider: %s", cfg.AI.Provider)
-		}
-		if cfg.AI.Model == "" {
-			return fmt.Errorf("ai model required when ai enabled")
-		}
-		if cfg.AI.APIKeyEnv == "" {
-			return fmt.Errorf("ai api_key_env required when ai enabled")
-		}
-	}
+	// AI 配置:仅校验开关,具体配置由存储提供
 
 	return nil
 }

+ 196 - 27
service/internal/modules/mcp_ai/API.md

@@ -1,6 +1,6 @@
 # AI Chat API
 
-简要说明:提供基于外部大模型(当前支持 OpenAI)的简单对话接口。默认路由前缀 `/ai`,统一使用 JSON 请求/响应,并沿用 `service/internal/common/response.Response` 包装。
+简要说明:提供基于外部大模型(OpenAI 兼容协议)的简单对话接口。默认路由前缀 `/ai`,统一使用 JSON 请求/响应,并沿用 `service/internal/common/response.Response` 包装。前端通过从存储表 `ai_config` 中选择一条配置(使用其 `id`,即 `config_id`)来指定要调用的 provider 与 model;API Key 存放在 `ai_config`(字段含 id/name/provider/model/base_url/api_key),不再依赖环境变量或在请求中传递密钥。TOML 仅配置是否启用 AI,所有连接信息由 `ai_config` 管理。
 
 ## 前置条件
 - 配置:`config.toml`
@@ -10,11 +10,8 @@
 
   [ai]
   enable = true
-  provider = "openai"
-  model = "gpt-4o-mini"
-  api_key_env = "OPENAI_API_KEY"
   ```
-- 环境变量:启动进程前导出对应的 Key,例如:`export OPENAI_API_KEY=sk-...`
+- 无需环境变量:API Key 直接存放在 `ai_config.api_key`,通过接口更新。
 - 构建/运行:`go build ./...` 或 `go run ./`
 
 ## 通用响应格式
@@ -30,53 +27,225 @@
 ## 接口一览
 | 方法 | 路径 | 功能 |
 | ---- | ---- | ---- |
-| POST | /ai/chat | 发送用户提示词,获取模型回复 |
+| POST | /ai/chat | 单一入口,mode 控制同步/流式/异步(支持 request_id 幂等与取消) |
+| POST | /ai/chat/cancel | 取消指定的聊天请求(按 request_id 或 session_id) |
+| POST | /ai/history | 按 session_id 查询对话历史 |
+| POST | /ai/history/delete | 删除指定 session 的历史 |
+| POST | /ai/config/get | 获取当前(默认)AI 配置 |
+| POST | /ai/config/list | 获取全部 AI 配置列表 |
+| POST | /ai/config/create | 新建 AI 配置(单行表,等价覆盖 default) |
+| POST | /ai/config/update | 更新 AI 配置,覆盖存储中的 ai_config |
+| POST | /ai/config/delete | 删除 AI 配置(默认 id=default,删除后需重新插入/更新) |
 
 ## POST /ai/chat
-- 功能:调用外部 AI 模型生成文本回复。
+- 功能:单一入口,`mode` 控制同步/流式/异步;支持 request_id 幂等、显式取消;成功后写入历史(user/assistant)
 - Content-Type: `application/json`
 
 ### 请求体
 | 字段 | 类型 | 必填 | 说明 |
 | ---- | ---- | :--: | ---- |
+| session_id | string | 是 | 会话 ID,用于归档历史(同一会话请复用) |
 | prompt | string | 是 | 用户输入内容 |
 | system | string | 否 | 系统指令/角色设定 |
-| model | string | 否 | 覆盖默认模型(不填则使用配置中的 `ai.model`) |
+| model | string | 否 | 覆盖配置中的 `model`(优先使用请求中的 `model`) |
+| config_id | string | 否 | 指定使用的 `ai_config.id`(优先于默认配置);推荐前端先调用 `/ai/config/list` 获取可用配置并传入该字段 |
+| request_id | string | 否 | 幂等与取消用:同一 request_id 重复调用直接命中缓存;取消也可用该 ID |
+| mode | string | 否 | `sync`(默认)、`stream`、`async` |
+| timeoutMs | int | 否 | 单次调用超时,毫秒;<=0 时默认 45s |
+
+### 响应
+- `mode=sync`(默认):`data = {"mode": "sync", "text": "...", "request_id": "..."}`
+- `mode=stream`:SSE 事件 `start` -> `message`/`error` -> `done`,header `Content-Type: text/event-stream`
+- `mode=async`:`data = {"mode": "async", "task_id": "...", "request_id": "..."}`
+
+### 说明与约束
+- 配置选择:若请求不带 `config_id`,服务会使用默认配置(可通过 `/ai/config/get` 查看);若带 `config_id`,服务会在 `ai_config` 中查找对应 `id` 并使用其 `base_url`/`api_key`/`provider`/`model` 发起调用;若找不到会返回错误。
+- Model 覆盖:请求中的 `model` 可临时覆盖配置中的模型字段(仅影响本次请求)。
+- 超时:默认 45 秒,可通过 `timeoutMs` 指定;取消或客户端断开会终止下游请求。
+- 历史:仅在模型调用成功后写入 user/assistant 消息到 `ai_chat_history`(字段含 session_id/role/content/model/provider/created_at)。
+- 幂等:携带 `request_id` 时,若之前已成功完成同一 ID 的请求,直接返回缓存结果,不重复计费。
+- 取消:通过 `/ai/chat/cancel` 或客户端断开 SSE/HTTP 连接可取消当前生成。
+- 安全:API Key 存储在 `ai_config.api_key`,前端通过配置接口写入;不要在 `/ai/chat` 请求体中携带密钥。
+
+### cURL 示例(同步)
+```bash
+curl -X POST http://127.0.0.1:8080/ai/chat \
+  -H "Content-Type: application/json" \
+  -d '{
+    "session_id": "demo-session-1",
+    "prompt": "解释 SQL 索引的作用",
+    "system": "你是数据库助手",
+    "config_id": "default",
+    "request_id": "req-123"
+  }'
+
+```
+
+### SSE 示例(流式)
+与同步示例相同请求体,增加 `"mode": "stream"`,前端用 EventSource/Fetch-SSE 读取事件。
+
+### 异步示例
+与同步示例相同请求体,增加 `"mode": "async"`;返回 task_id/request_id,结果由任务订阅或轮询获取。
+
+## POST /ai/chat/cancel
+- 功能:取消指定的聊天请求。
+- Content-Type: `application/json`
+
+### 请求体
+| 字段 | 类型 | 必填 | 说明 |
+| ---- | ---- | :--: | ---- |
+| request_id | string | 否 | 优先匹配该 ID 对应的请求 |
+| session_id | string | 否 | 备用匹配;两者至少填一个 |
+
+### 成功响应
+```json
+{ "code": 0, "msg": "ok" }
+```
+
+### 说明
+- 若找不到对应的进行中请求会返回错误;取消成功后下游 HTTP 调用会被终止。
+
+## POST /ai/history
+### 请求体
+| 字段 | 类型 | 必填 | 说明 |
+| ---- | ---- | :--: | ---- |
+| session_id | string | 是 | 会话 ID |
+| limit | int | 否 | 返回条数,默认 100 |
+| offset | int | 否 | 偏移量,默认 0 |
+
+### 成功响应
+`data` 字段为:
+```json
+{
+  "messages": [
+    {"id": "u1", "session_id": "demo-session-1", "role": "user", "content": "hi", "model": "gpt-4o-mini", "provider": "openai", "created_at": "2024-01-01T00:00:00Z"},
+    {"id": "a1", "session_id": "demo-session-1", "role": "assistant", "content": "hello", "model": "gpt-4o-mini", "provider": "openai", "created_at": "2024-01-01T00:00:01Z"}
+  ]
+}
+```
+
+
+## POST /ai/history/delete
+- 功能:删除指定 session 的全部历史记录。
+- Content-Type: `application/json`
+
+### 请求体
+| 字段 | 类型 | 必填 | 说明 |
+| ---- | ---- | :--: | ---- |
+| session_id | string | 是 | 要删除的会话 ID |
+### 成功响应
+```json
+{ "code": 0, "msg": "ok" }
+```
+
+## MCP ai_chat(补充说明)
+当 `[mcp].enable=true` 时,MCP 服务会注册工具 `ai_chat`(与 HTTP 路由独立)。参数含义与 HTTP 相同,客户端通过 MCP 协议的 `tools/call` 请求调用。
+
+示例(MCP tools/call)
+```json
+{
+  "method": "tools/call",
+  "params": {
+    "name": "ai_chat",
+    "arguments": {
+      "prompt": "写一个创建表的 SQL",
+      "config_id": "default",
+      "model": "deepseek-chat"
+    }
+  }
+}
+```
+
+## POST /ai/config/get
+- 功能:获取当前(默认)AI 配置,返回单条记录。
+- Content-Type: `application/json`
+- 请求体:无
+
+### 成功响应
+`data` 字段为当前配置:
+```json
+{
+  "ai_settings": {
+    "id": "default",
+    "name": "default",
+    "provider": "openai",
+    "model": "gpt-4o-mini",
+    "base_url": "https://api.openai.com/v1/chat/completions",
+    "api_key": "sk-xxx",
+    "updated_at": "2024-01-01T00:00:00Z"
+  }
+}
+```
+
+## POST /ai/config/list
+- 功能:获取全部 AI 配置列表(按 updated_at 降序)。
+- Content-Type: `application/json`
+- 请求体:无
 
 ### 成功响应
 `data` 字段为:
 ```json
 {
-  "text": "模型生成的回复文本"
+  "items": [
+    {"id": "default", "name": "default", "provider": "openai", "model": "gpt-4o-mini", "base_url": "https://api.openai.com/v1/chat/completions", "api_key": "sk-xxx", "updated_at": "2024-01-01T00:00:00Z"}
+  ]
 }
 ```
-完整示例:
+
+## POST /ai/config/update
+- 功能:部分更新 AI 配置;仅覆盖请求体中非空字段,其余沿用当前存储值。
+- Content-Type: `application/json`
+
+### 请求体(示例,仅更新 model)
 ```json
 {
-  "code": 0,
-  "msg": "ok",
-  "data": {"text": "SQL 索引用于加速查询..."}
+  "ai_settings": {
+    "model": "gpt-4o-mini"
+  }
 }
 ```
 
-### 失败示例
+### 成功响应
+`data` 同 /ai/config/get。
+
+说明与约束:
+- 非空字段才会更新;name/provider/model/base_url/api_key 为空时保持原值。
+- 若存储中值为空,仍会回落默认 name=default、provider=openai、model=gpt-4o-mini、base_url=官方地址。
+- `model`、`api_key` 最终需要有值(若为空则依赖已有或默认值)。
+- 更新后即写入 `ai_config`,后续 /ai/chat 和 MCP ai_chat 调用均使用新配置。
+
+## POST /ai/config/create
+- 功能:新建 AI 配置;若未传 id 后端自动生成,若指定 id 已存在则报错(不覆盖)。
+- Content-Type: `application/json`
+
+### 请求体
 ```json
 {
-  "code": 2,
-  "msg": "AI 调用失败",
-  "error": "openai request failed: status 401"
+  "ai_settings": {
+    "name": "default",
+    "provider": "openai",
+    "model": "gpt-4o-mini",
+    "base_url": "https://api.openai.com/v1/chat/completions",
+    "api_key": "sk-xxx"
+  }
 }
 ```
 
-### cURL 示例
-```bash
-curl -X POST http://127.0.0.1:8080/ai/chat \
-  -H "Content-Type: application/json" \
-  -d '{
-    "prompt": "解释 SQL 索引的作用",
-    "system": "你是数据库助手"
-  }'
+### 成功响应
+同 `/ai/config/update`。
+
+说明:允许多条配置记录,但客户端通常使用 id/name=default。
+
+## POST /ai/config/delete
+- 功能:删除指定 AI 配置记录(默认删除 id=default)。删除后需要用 /ai/config/create 或 /ai/config/update 重新写入。
+- Content-Type: `application/json`
+
+### 请求体
+```json
+{ "id": "default" }
 ```
 
-## MCP ai_chat(补充说明)
-当 `[mcp].enable=true` 时,MCP 服务会注册工具 `ai_chat`(与 HTTP 路由独立)。参数含义与 HTTP 相同,客户端通过 MCP 协议的 `tools/call` 请求调用。
+### 成功响应
+```json
+{ "code": 0, "msg": "ok" }
+```

+ 64 - 5
service/internal/modules/mcp_ai/api/types.go

@@ -1,15 +1,74 @@
 package api
 
+import storageTypes "dbview/service/internal/common/manager/storage/types"
+
 // ChatRequest AI 聊天请求
-// prompt 为必填,system/model 可选。
+// session_id 用于归档会话历史,prompt 为必填,system/model 可选。
 type ChatRequest struct {
-	Prompt string `json:"prompt" binding:"required"`
-	System string `json:"system,omitempty"`
-	Model  string `json:"model,omitempty"`
+	SessionID string `json:"session_id" binding:"required"`
+	Prompt    string `json:"prompt" binding:"required"`
+	System    string `json:"system,omitempty"`
+	Model     string `json:"model,omitempty"`
+	ConfigID  string `json:"config_id,omitempty"`
+	RequestID string `json:"request_id,omitempty"`
+	Mode      string `json:"mode,omitempty"`
+	TimeoutMs int    `json:"timeoutMs,omitempty"`
 }
 
 // ChatResponse AI 聊天响应
 // text 为生成的回复内容。
 type ChatResponse struct {
-	Text string `json:"text"`
+	Mode      string `json:"mode"`
+	Text      string `json:"text,omitempty"`
+	TaskID    string `json:"task_id,omitempty"`
+	RequestID string `json:"request_id,omitempty"`
+}
+
+// CancelChatRequest 取消正在进行的聊天请求。
+// 至少提供 request_id 或 session_id 之一,用于定位正在执行的请求。
+type CancelChatRequest struct {
+	RequestID string `json:"request_id"`
+	SessionID string `json:"session_id"`
+}
+
+// HistoryRequest 查询指定 session 的对话历史。
+type HistoryRequest struct {
+	SessionID string `json:"session_id" binding:"required"`
+	Limit     int    `json:"limit"`
+	Offset    int    `json:"offset"`
+}
+
+// HistoryResponse 返回对话消息列表。
+type HistoryResponse struct {
+	Messages []storageTypes.AIMessage `json:"messages"`
+}
+
+// DeleteHistoryRequest 删除指定 session 的历史。
+type DeleteHistoryRequest struct {
+	SessionID string `json:"session_id" binding:"required"`
+}
+
+// UpdateConfigRequest 请求覆盖 AI 配置;provider 可空则回退默认 openai,base_url 为空回退官方地址。
+type UpdateConfigRequest struct {
+	storageTypes.AISettings `json:"ai_settings" binding:"required"`
+}
+
+// ConfigResponse 返回当前 AI 配置。
+type ConfigResponse struct {
+	storageTypes.AISettings `json:"ai_settings"`
+}
+
+// ConfigListResponse 返回所有 AI 配置。
+type ConfigListResponse struct {
+	Items []storageTypes.AISettings `json:"items"`
+}
+
+// DeleteConfigRequest 删除指定 id(空则默认 default)。
+type DeleteConfigRequest struct {
+	ID string `json:"id"`
+}
+
+// CreateConfigRequest 创建 AI 配置。
+type CreateConfigRequest struct {
+	storageTypes.AISettings `json:"ai_settings" binding:"required"`
 }

+ 195 - 7
service/internal/modules/mcp_ai/handler/handler.go

@@ -1,9 +1,8 @@
 package handler
 
 import (
-	"context"
 	"net/http"
-	"time"
+	"strings"
 
 	"dbview/service/internal/common/response"
 	"dbview/service/internal/modules/mcp_ai/api"
@@ -23,6 +22,10 @@ func NewHandler(svc *service.AIService) *Handler {
 }
 
 // Chat 提供 POST /ai/chat 接口。
+// 支持三种模式:
+// - sync(默认):同步返回完整文本。
+// - stream:SSE 流式返回(同一路径,header 设为 text/event-stream)。
+// - async:创建后台任务立即返回 task_id/request_id。
 func (h *Handler) Chat(c *gin.Context) {
 	var req api.ChatRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -30,14 +33,199 @@ func (h *Handler) Chat(c *gin.Context) {
 		return
 	}
 
-	ctx, cancel := context.WithTimeout(c.Request.Context(), 45*time.Second)
-	defer cancel()
+	mode := strings.ToLower(strings.TrimSpace(req.Mode))
+	if mode == "" {
+		mode = "sync"
+	}
+
+	ctx := c.Request.Context()
+
+	switch mode {
+	case "async":
+		taskObj, requestID, err := h.svc.StartChatTask(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs)
+		if err != nil {
+			c.JSON(http.StatusBadRequest, response.Response{Code: 2, Msg: "创建任务失败", Error: err.Error()})
+			return
+		}
+		c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ChatResponse{Mode: "async", TaskID: taskObj.ID, RequestID: requestID}})
+		return
+
+	case "stream":
+		c.Writer.Header().Set("Content-Type", "text/event-stream")
+		c.Writer.Header().Set("Cache-Control", "no-cache")
+		c.Writer.Header().Set("Connection", "keep-alive")
+
+		flusher, ok := c.Writer.(http.Flusher)
+		if !ok {
+			c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "AI 调用失败", Error: "stream not supported"})
+			return
+		}
+
+		resultCh := make(chan struct {
+			text string
+			err  error
+		}, 1)
+
+		go func() {
+			text, err := h.svc.Chat(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs)
+			resultCh <- struct {
+				text string
+				err  error
+			}{text: text, err: err}
+		}()
+
+		c.SSEvent("start", gin.H{"request_id": req.RequestID})
+		flusher.Flush()
+		select {
+		case <-ctx.Done():
+			c.SSEvent("error", gin.H{"error": ctx.Err().Error()})
+			flusher.Flush()
+			return
+		case res := <-resultCh:
+			if res.err != nil {
+				c.SSEvent("error", gin.H{"error": res.err.Error()})
+				flusher.Flush()
+				return
+			}
+			c.SSEvent("message", gin.H{"text": res.text})
+			flusher.Flush()
+			c.SSEvent("done", gin.H{"request_id": req.RequestID})
+			flusher.Flush()
+		}
+		return
+
+	default:
+		text, err := h.svc.Chat(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs)
+		if err != nil {
+			c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "AI 调用失败", Error: err.Error()})
+			return
+		}
+		c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ChatResponse{Mode: "sync", Text: text, RequestID: req.RequestID}})
+	}
+}
 
-	text, err := h.svc.Chat(ctx, req.Prompt, req.System, req.Model)
+// CancelChat 提供 POST /ai/chat/cancel 接口,取消指定聊天请求。
+func (h *Handler) CancelChat(c *gin.Context) {
+	var req api.CancelChatRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	if req.RequestID == "" && req.SessionID == "" {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: "request_id 或 session_id 必填其一"})
+		return
+	}
+
+	if err := h.svc.CancelChat(req.RequestID, req.SessionID); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 2, Msg: "取消失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"})
+}
+
+// History 提供 POST /ai/history 接口,查询指定 session 的消息历史,按时间正序返回。
+func (h *Handler) History(c *gin.Context) {
+	var req api.HistoryRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	msgs, err := h.svc.ListHistory(c.Request.Context(), req.SessionID, req.Limit, req.Offset)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "AI 调用失败", Error: err.Error()})
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取历史失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.HistoryResponse{Messages: msgs}})
+}
+
+// DeleteHistory 提供 POST /ai/history/delete 接口,删除指定 session 的全部历史。
+func (h *Handler) DeleteHistory(c *gin.Context) {
+	var req api.DeleteHistoryRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	if err := h.svc.DeleteHistory(c.Request.Context(), req.SessionID); err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "删除历史失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"})
+}
+
+// GetConfig 提供 POST /ai/config/get 接口,返回当前 AI 配置。
+func (h *Handler) GetConfig(c *gin.Context) {
+	cfg, err := h.svc.GetConfig(c.Request.Context())
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取 AI 配置失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *cfg}})
+}
+
+// ListConfigs 提供 POST /ai/config/list 接口,返回全部 AI 配置记录。
+func (h *Handler) ListConfigs(c *gin.Context) {
+	items, err := h.svc.ListConfigs(c.Request.Context())
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取 AI 配置列表失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigListResponse{Items: items}})
+}
+
+// UpdateConfig 提供 POST /ai/config/update 接口,允许用户覆盖 AI 配置。
+func (h *Handler) UpdateConfig(c *gin.Context) {
+	var req api.UpdateConfigRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	updated, err := h.svc.UpdateConfig(c.Request.Context(), req.AISettings)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "更新 AI 配置失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *updated}})
+}
+
+// CreateConfig 提供 POST /ai/config/create 接口,创建/覆盖 AI 配置。
+func (h *Handler) CreateConfig(c *gin.Context) {
+	var req api.CreateConfigRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	created, err := h.svc.CreateConfig(c.Request.Context(), req.AISettings)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "创建 AI 配置失败", Error: err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *created}})
+}
+
+// DeleteConfig 提供 POST /ai/config/delete 接口,删除指定 AI 配置(默认 default)。
+func (h *Handler) DeleteConfig(c *gin.Context) {
+	var req api.DeleteConfigRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()})
+		return
+	}
+
+	if err := h.svc.DeleteConfig(c.Request.Context(), req.ID); err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "删除 AI 配置失败", Error: err.Error()})
 		return
 	}
 
-	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ChatResponse{Text: text}})
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"})
 }

+ 8 - 0
service/internal/modules/mcp_ai/route.go

@@ -17,5 +17,13 @@ func RegisterRoutes(r *gin.Engine, svcObj *svc.AIService) {
 	grp := r.Group("/ai")
 	{
 		grp.POST("/chat", h.Chat)
+		grp.POST("/chat/cancel", h.CancelChat)
+		grp.POST("/history", h.History)
+		grp.POST("/history/delete", h.DeleteHistory)
+		grp.POST("/config/get", h.GetConfig)
+		grp.POST("/config/list", h.ListConfigs)
+		grp.POST("/config/create", h.CreateConfig)
+		grp.POST("/config/update", h.UpdateConfig)
+		grp.POST("/config/delete", h.DeleteConfig)
 	}
 }

+ 178 - 0
service/internal/modules/mcp_ai/service/providers.go

@@ -0,0 +1,178 @@
+package service
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+
+	"dbview/service/internal/common/manager/storage/types"
+)
+
+// ProviderClient 抽象不同 AI 提供商的调用方式
+type ProviderClient interface {
+	Chat(ctx context.Context, hc *http.Client, cfg types.AISettings, model, system, prompt string) (string, error)
+}
+
+// provider registry
+var providerRegistry = map[string]ProviderClient{}
+
+func registerProvider(name string, c ProviderClient) {
+	providerRegistry[strings.ToLower(strings.TrimSpace(name))] = c
+}
+
+func getProviderClient(name string) ProviderClient {
+	return providerRegistry[strings.ToLower(strings.TrimSpace(name))]
+}
+
+// OpenAIProvider 实现 OpenAI 兼容的 Chat 调用,但读取 cfg 中的 auth/header 设定
+type OpenAIProvider struct{}
+
+func (p *OpenAIProvider) Chat(ctx context.Context, hc *http.Client, cfg types.AISettings, model, system, prompt string) (string, error) {
+	messages := []openAIMessage{}
+	if system != "" {
+		messages = append(messages, openAIMessage{Role: "system", Content: system})
+	}
+	messages = append(messages, openAIMessage{Role: "user", Content: prompt})
+
+	body := openAIChatRequest{Model: model, Messages: messages}
+	b, err := json.Marshal(body)
+	if err != nil {
+		return "", err
+	}
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, cfg.BaseURL, bytes.NewReader(b))
+	if err != nil {
+		return "", err
+	}
+
+	headerName := cfg.AuthHeader
+	if headerName == "" {
+		headerName = "Authorization"
+	}
+	if cfg.AuthScheme != "" {
+		req.Header.Set(headerName, cfg.AuthScheme+" "+cfg.APIKey)
+	} else {
+		req.Header.Set(headerName, cfg.APIKey)
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	// extra headers JSON
+	if cfg.ExtraHeaders != "" {
+		var extra map[string]string
+		if err := json.Unmarshal([]byte(cfg.ExtraHeaders), &extra); err == nil {
+			for k, v := range extra {
+				if k != "Content-Type" {
+					req.Header.Set(k, v)
+				}
+			}
+		}
+	}
+
+	resp, err := hc.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	raw, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+
+	var out openAIChatResponse
+	_ = json.Unmarshal(raw, &out)
+
+	if resp.StatusCode >= 300 {
+		if out.Error != nil && out.Error.Message != "" {
+			return "", fmt.Errorf("%s error: %s (status=%d)", cfg.RequestType, out.Error.Message, resp.StatusCode)
+		}
+		snippet := string(raw)
+		if len(snippet) > 500 {
+			snippet = snippet[:500] + "..."
+		}
+		return "", fmt.Errorf("%s request failed: status=%d, body=%s", cfg.RequestType, resp.StatusCode, snippet)
+	}
+
+	if len(out.Choices) == 0 {
+		return "", fmt.Errorf("%s 返回为空(status=%d)", cfg.RequestType, resp.StatusCode)
+	}
+
+	return out.Choices[0].Message.Content, nil
+}
+
+// MimoProvider 简单实现(当作示例):使用 body {"input": <prompt>},并支持自定义 header 名称/无 scheme
+type MimoProvider struct{}
+
+func (p *MimoProvider) Chat(ctx context.Context, hc *http.Client, cfg types.AISettings, model, system, prompt string) (string, error) {
+	// 简单 body:{"input":"..."}
+	payload := map[string]any{"input": prompt}
+	b, err := json.Marshal(payload)
+	if err != nil {
+		return "", err
+	}
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, cfg.BaseURL, bytes.NewReader(b))
+	if err != nil {
+		return "", err
+	}
+
+	headerName := cfg.AuthHeader
+	if headerName == "" {
+		headerName = "x-api-key"
+	}
+	if cfg.AuthScheme != "" {
+		req.Header.Set(headerName, cfg.AuthScheme+" "+cfg.APIKey)
+	} else {
+		req.Header.Set(headerName, cfg.APIKey)
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	if cfg.ExtraHeaders != "" {
+		var extra map[string]string
+		if err := json.Unmarshal([]byte(cfg.ExtraHeaders), &extra); err == nil {
+			for k, v := range extra {
+				if k != "Content-Type" {
+					req.Header.Set(k, v)
+				}
+			}
+		}
+	}
+
+	resp, err := hc.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	raw, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+
+	// 尝试解析常见字段,否则返回原始 body 文本
+	var parsed map[string]any
+	if err := json.Unmarshal(raw, &parsed); err == nil {
+		// 常见字段:output / answer / data
+		if v, ok := parsed["output"]; ok {
+			return fmt.Sprint(v), nil
+		}
+		if v, ok := parsed["answer"]; ok {
+			return fmt.Sprint(v), nil
+		}
+		if v, ok := parsed["data"]; ok {
+			return fmt.Sprint(v), nil
+		}
+	}
+
+	// fallback to raw string
+	return strings.TrimSpace(string(raw)), nil
+}
+
+func init() {
+	registerProvider("openai", &OpenAIProvider{})
+	registerProvider("mimo", &MimoProvider{})
+}

+ 433 - 33
service/internal/modules/mcp_ai/service/service.go

@@ -5,55 +5,448 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
 	"net/http"
-	"os"
+	"strings"
+	"sync"
+	"time"
 
-	"dbview/service/internal/config"
+	"dbview/service/internal/common/manager/storage"
+	"dbview/service/internal/common/manager/storage/types"
+	"dbview/service/internal/common/manager/task"
+
+	"github.com/google/uuid"
 )
 
-// AIService 提供 AI 聊天能力(当前支持 OpenAI)。
+// AIService 提供 AI 聊天能力(开关来自 TOML,其余配置从存储读取)。
 type AIService struct {
-	cfg config.AIConfig
-	hc  *http.Client
+	enabled         bool
+	store           storage.StorageInterface
+	hc              *http.Client
+	taskMgr         *task.Manager
+	cancelMu        sync.Mutex
+	cancelMap       map[string]context.CancelFunc
+	cacheMu         sync.Mutex
+	respCache       map[string]cachedResponse
+	cacheTTL        time.Duration
+	maxCacheEntries int
+}
+
+type cachedResponse struct {
+	text      string
+	createdAt time.Time
+}
+
+// NewAIService 创建 AIService。
+func NewAIService(enabled bool, store storage.StorageInterface, taskMgr *task.Manager) *AIService {
+	return &AIService{
+		enabled:         enabled,
+		store:           store,
+		hc:              http.DefaultClient,
+		taskMgr:         taskMgr,
+		cancelMap:       make(map[string]context.CancelFunc),
+		respCache:       make(map[string]cachedResponse),
+		cacheTTL:        10 * time.Minute,
+		maxCacheEntries: 100,
+	}
+}
+
+// GetConfig 返回当前存储中的 AI 配置(需要开启 ai 开关)。
+func (s *AIService) GetConfig(ctx context.Context) (*types.AISettings, error) {
+	if !s.enabled {
+		return nil, fmt.Errorf("ai 功能未启用")
+	}
+	return s.loadConfig()
 }
 
-// NewAIService 创建 AIService,需传入配置。
-func NewAIService(cfg config.AIConfig) *AIService {
-	return &AIService{cfg: cfg, hc: http.DefaultClient}
+// ListConfigs 返回所有已保存的 AI 配置记录。
+func (s *AIService) ListConfigs(ctx context.Context) ([]types.AISettings, error) {
+	if !s.enabled {
+		return nil, fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return nil, fmt.Errorf("ai 配置存储未初始化")
+	}
+	return s.store.ListAIConfigs()
 }
 
-// Chat 调用外部模型生成回复。
-func (s *AIService) Chat(ctx context.Context, prompt, system, modelOverride string) (string, error) {
-	if !s.cfg.Enable {
+// UpdateConfig 覆盖存储中的 AI 配置,允许用户自定义 provider/model/base_url/api_key。
+func (s *AIService) UpdateConfig(ctx context.Context, cfg types.AISettings) (*types.AISettings, error) {
+	if !s.enabled {
+		return nil, fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return nil, fmt.Errorf("ai 配置存储未初始化")
+	}
+
+	current, err := s.store.GetAIConfig()
+	if err != nil {
+		return nil, err
+	}
+
+	final := *current
+	if val := strings.TrimSpace(cfg.Name); val != "" {
+		final.Name = val
+	}
+	if val := strings.TrimSpace(cfg.Provider); val != "" {
+		final.Provider = val
+	}
+	if val := strings.TrimSpace(cfg.Model); val != "" {
+		final.Model = val
+	}
+	if val := strings.TrimSpace(cfg.BaseURL); val != "" {
+		final.BaseURL = val
+	}
+	if val := strings.TrimSpace(cfg.APIKey); val != "" {
+		final.APIKey = val
+	}
+
+	// 确保基本默认值存在
+	if final.Provider == "" {
+		final.Provider = "openai"
+	}
+	if final.Model == "" {
+		final.Model = "gpt-4o-mini"
+	}
+	if final.BaseURL == "" {
+		final.BaseURL = "https://api.openai.com/v1/chat/completions"
+	}
+	if final.Name == "" {
+		final.Name = "default"
+	}
+
+	return s.store.UpdateAIConfig(final)
+}
+
+// CreateConfig 新建 AI 配置(当前单行表,等价于创建/覆盖 default)。
+func (s *AIService) CreateConfig(ctx context.Context, cfg types.AISettings) (*types.AISettings, error) {
+	if !s.enabled {
+		return nil, fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return nil, fmt.Errorf("ai 配置存储未初始化")
+	}
+
+	// 归一化并填充默认值
+	cfg.Name = strings.TrimSpace(cfg.Name)
+	if cfg.Name == "" {
+		cfg.Name = "default"
+	}
+	cfg.Provider = strings.TrimSpace(cfg.Provider)
+	if cfg.Provider == "" {
+		cfg.Provider = "openai"
+	}
+	cfg.Model = strings.TrimSpace(cfg.Model)
+	if cfg.Model == "" {
+		cfg.Model = "gpt-4o-mini"
+	}
+	cfg.BaseURL = strings.TrimSpace(cfg.BaseURL)
+	if cfg.BaseURL == "" {
+		cfg.BaseURL = "https://api.openai.com/v1/chat/completions"
+	}
+	cfg.APIKey = strings.TrimSpace(cfg.APIKey) // 可为空
+
+	return s.store.CreateAIConfig(cfg)
+}
+
+// DeleteConfig 删除指定 id 的 AI 配置(默认 id=default)。
+func (s *AIService) DeleteConfig(ctx context.Context, id string) error {
+	if !s.enabled {
+		return fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return fmt.Errorf("ai 配置存储未初始化")
+	}
+	if id == "" {
+		id = "default"
+	}
+	return s.store.DeleteAIConfig(id)
+}
+
+// Chat 调用外部模型生成回复,支持按请求覆盖 provider/model,并在成功后写入历史;支持 request_id 幂等、取消、超时。
+func (s *AIService) Chat(ctx context.Context, sessionID, prompt, system, modelOverride, configID, requestID string, timeoutMs int) (string, error) {
+	if !s.enabled {
 		return "", fmt.Errorf("ai 功能未启用")
 	}
+	if s.store == nil {
+		return "", fmt.Errorf("ai 配置存储未初始化")
+	}
+	sessionID = strings.TrimSpace(sessionID)
+	if sessionID == "" {
+		return "", fmt.Errorf("session_id 不能为空")
+	}
+	prompt = strings.TrimSpace(prompt)
+	if prompt == "" {
+		return "", fmt.Errorf("prompt 不能为空")
+	}
+	configID = strings.TrimSpace(configID)
+	modelOverride = strings.TrimSpace(modelOverride)
+	requestID = strings.TrimSpace(requestID)
 
-	if s.cfg.Provider != "openai" {
-		return "", fmt.Errorf("ai provider 未支持: %s", s.cfg.Provider)
+	// 幂等:若带 request_id 且已有成功缓存,直接返回
+	if requestID != "" {
+		if text, ok := s.getCachedResponse(requestID); ok {
+			return text, nil
+		}
 	}
 
-	apiKeyEnv := s.cfg.APIKeyEnv
-	if apiKeyEnv == "" {
-		apiKeyEnv = "OPENAI_API_KEY"
+	key := requestID
+	if key == "" {
+		key = sessionID
 	}
-	apiKey := os.Getenv(apiKeyEnv)
-	if apiKey == "" {
-		return "", fmt.Errorf("未找到 AI API Key 环境变量 %s", apiKeyEnv)
+	if key == "" {
+		return "", fmt.Errorf("request_id 或 session_id 不能为空")
 	}
 
-	model := s.cfg.Model
-	if model == "" {
-		model = "gpt-4o-mini"
+	effective := time.Duration(timeoutMs) * time.Millisecond
+	if effective <= 0 {
+		effective = 45 * time.Second
+	}
+	ctxChat, cancel := context.WithTimeout(ctx, effective)
+	if err := s.registerCancel(key, cancel); err != nil {
+		return "", err
 	}
+	defer cancel()
+	defer s.clearCancel(key)
+
+	cfg, err := s.loadConfig()
+	if err != nil {
+		return "", err
+	}
+
+	// 支持按请求指定 provider:若请求中带 provider,则尝试在存储中查找对应的配置并使用之。
+	usedCfg := cfg
+	if configID != "" {
+		configs, err := s.store.ListAIConfigs()
+		if err != nil {
+			return "", fmt.Errorf("查找 ai_config 列表失败: %w", err)
+		}
+		var found *types.AISettings
+		for i := range configs {
+			if strings.EqualFold(strings.TrimSpace(configs[i].ID), strings.TrimSpace(configID)) {
+				found = &configs[i]
+				break
+			}
+		}
+		if found == nil {
+			return "", fmt.Errorf("config_id 未配置: %s", configID)
+		}
+		usedCfg = found
+	}
+
+	if usedCfg.APIKey == "" {
+		return "", fmt.Errorf("ai api_key 未配置(provider=%s, model=%s)", usedCfg.Provider, usedCfg.Model)
+	}
+
+	model := usedCfg.Model
 	if modelOverride != "" {
 		model = modelOverride
 	}
+	if model == "" {
+		return "", fmt.Errorf("ai model 未配置(provider=%s)", usedCfg.Provider)
+	}
+
+	// 通过 provider adapter 调用外部模型(由 ai_config 中的 request_type 或 provider 字段决定)
+	// 优先使用 RequestType,再回退到 Provider 字段
+	providerName := strings.ToLower(strings.TrimSpace(usedCfg.RequestType))
+	if providerName == "" {
+		providerName = strings.ToLower(strings.TrimSpace(usedCfg.Provider))
+	}
+	client := getProviderClient(providerName)
+	if client == nil {
+		return "", fmt.Errorf("unsupported provider/request_type: %s", providerName)
+	}
 
-	return callOpenAIChat(ctx, s.hc, apiKey, model, system, prompt)
+	text, err := client.Chat(ctxChat, s.hc, *usedCfg, model, system, prompt)
+	if err != nil {
+		return "", fmt.Errorf("调用模型失败(provider=%s, model=%s): %w", providerName, model, err)
+	}
+
+	// 成功后再写入历史,避免失败时产生多余 user 记录
+	userMsg := types.AIMessage{SessionID: sessionID, Role: "user", Content: prompt, Model: model, Provider: usedCfg.Provider}
+	if err := s.store.SaveAIMessage(&userMsg); err != nil {
+		return "", err
+	}
+	aiMsg := types.AIMessage{SessionID: sessionID, Role: "assistant", Content: text, Model: model, Provider: usedCfg.Provider}
+	if err := s.store.SaveAIMessage(&aiMsg); err != nil {
+		return "", err
+	}
+
+	if requestID != "" {
+		s.putCachedResponse(requestID, text)
+	}
+
+	return text, nil
+}
+
+// StartChatTask 使用任务管理器异步执行 Chat,返回任务对象和实际使用的 request_id。
+// 说明:
+// - 需要在构造时注入 taskMgr,否则返回错误。
+// - 若未提供 request_id,则自动生成;后续取消/幂等使用该 ID。
+func (s *AIService) StartChatTask(ctx context.Context, sessionID, prompt, system, modelOverride, configID, requestID string, timeoutMs int) (*task.Task, string, error) {
+	if s.taskMgr == nil {
+		return nil, "", fmt.Errorf("task manager 未初始化")
+	}
+
+	reqID := strings.TrimSpace(requestID)
+	if reqID == "" {
+		reqID = uuid.New().String()[:8]
+	}
+
+	// 提交任务,任务内部调用 Chat,并复用 request_id 以支持取消/幂等。
+	t := s.taskMgr.SubmitTask("ai_chat", func(taskCtx context.Context, progress func(int)) (any, error) {
+		progress(5)
+		text, err := s.Chat(taskCtx, sessionID, prompt, system, modelOverride, configID, reqID, timeoutMs)
+		if err != nil {
+			progress(100)
+			return nil, err
+		}
+		progress(100)
+		return text, nil
+	})
+
+	return t, reqID, nil
 }
 
-// callOpenAIChat performs a minimal chat completion request.
-func callOpenAIChat(ctx context.Context, hc *http.Client, apiKey, model, systemPrompt, userPrompt string) (string, error) {
+// CancelChat 取消正在执行的聊天请求(基于 request_id 优先,其次 session_id)。
+func (s *AIService) CancelChat(requestID, sessionID string) error {
+	key := strings.TrimSpace(requestID)
+	if key == "" {
+		key = strings.TrimSpace(sessionID)
+	}
+	if key == "" {
+		return fmt.Errorf("request_id 或 session_id 不能为空")
+	}
+
+	s.cancelMu.Lock()
+	cancel, ok := s.cancelMap[key]
+	if ok {
+		delete(s.cancelMap, key)
+	}
+	s.cancelMu.Unlock()
+
+	if !ok {
+		return fmt.Errorf("未找到可取消的请求: %s", key)
+	}
+
+	cancel()
+	return nil
+}
+
+func (s *AIService) registerCancel(key string, cancel context.CancelFunc) error {
+	if key == "" {
+		return fmt.Errorf("request_id 或 session_id 不能为空")
+	}
+	s.cancelMu.Lock()
+	defer s.cancelMu.Unlock()
+	if _, exists := s.cancelMap[key]; exists {
+		return fmt.Errorf("已有正在执行的请求: %s", key)
+	}
+	s.cancelMap[key] = cancel
+	return nil
+}
+
+func (s *AIService) clearCancel(key string) {
+	if key == "" {
+		return
+	}
+	s.cancelMu.Lock()
+	delete(s.cancelMap, key)
+	s.cancelMu.Unlock()
+}
+
+func (s *AIService) getCachedResponse(id string) (string, bool) {
+	id = strings.TrimSpace(id)
+	if id == "" {
+		return "", false
+	}
+	s.cacheMu.Lock()
+	defer s.cacheMu.Unlock()
+	entry, ok := s.respCache[id]
+	if !ok {
+		return "", false
+	}
+	if time.Since(entry.createdAt) > s.cacheTTL {
+		delete(s.respCache, id)
+		return "", false
+	}
+	return entry.text, true
+}
+
+func (s *AIService) putCachedResponse(id, text string) {
+	id = strings.TrimSpace(id)
+	if id == "" {
+		return
+	}
+	s.cacheMu.Lock()
+	defer s.cacheMu.Unlock()
+	// 清理过期
+	for k, v := range s.respCache {
+		if time.Since(v.createdAt) > s.cacheTTL {
+			delete(s.respCache, k)
+		}
+	}
+	if len(s.respCache) >= s.maxCacheEntries {
+		// 简单淘汰最早的一个
+		var oldestKey string
+		var oldestTime time.Time
+		for k, v := range s.respCache {
+			if oldestKey == "" || v.createdAt.Before(oldestTime) {
+				oldestKey = k
+				oldestTime = v.createdAt
+			}
+		}
+		if oldestKey != "" {
+			delete(s.respCache, oldestKey)
+		}
+	}
+	s.respCache[id] = cachedResponse{text: text, createdAt: time.Now()}
+}
+
+// ListHistory 返回指定 session 的历史记录。
+// 用于前端分页加载聊天记录,默认每页 100 条,可通过 limit/offset 控制。
+func (s *AIService) ListHistory(ctx context.Context, sessionID string, limit, offset int) ([]types.AIMessage, error) {
+	if !s.enabled {
+		return nil, fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return nil, fmt.Errorf("ai 配置存储未初始化")
+	}
+	sessionID = strings.TrimSpace(sessionID)
+	if sessionID == "" {
+		return nil, fmt.Errorf("session_id 不能为空")
+	}
+
+	return s.store.ListAIHistory(sessionID, limit, offset)
+}
+
+// DeleteHistory 删除指定 session 的全部历史。
+// 用于“清空聊天”场景,调用后该会话记录将全部移除。
+func (s *AIService) DeleteHistory(ctx context.Context, sessionID string) error {
+	if !s.enabled {
+		return fmt.Errorf("ai 功能未启用")
+	}
+	if s.store == nil {
+		return fmt.Errorf("ai 配置存储未初始化")
+	}
+	sessionID = strings.TrimSpace(sessionID)
+	if sessionID == "" {
+		return fmt.Errorf("session_id 不能为空")
+	}
+
+	return s.store.DeleteAIHistory(sessionID)
+}
+
+func (s *AIService) loadConfig() (*types.AISettings, error) {
+	if s.store == nil {
+		return nil, fmt.Errorf("ai 配置存储未初始化")
+	}
+	return s.store.GetAIConfig()
+}
+
+// callOpenAICompatibleChat performs a minimal chat completion request (OpenAI-compatible schema).
+// 返回更详细的错误,包括状态码与响应体片段,便于排查。
+func callOpenAICompatibleChat(ctx context.Context, hc *http.Client, apiKey, baseURL, model, systemPrompt, userPrompt, provider string) (string, error) {
 	messages := []openAIMessage{}
 	if systemPrompt != "" {
 		messages = append(messages, openAIMessage{Role: "system", Content: systemPrompt})
@@ -66,7 +459,7 @@ func callOpenAIChat(ctx context.Context, hc *http.Client, apiKey, model, systemP
 		return "", err
 	}
 
-	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/chat/completions", bytes.NewReader(b))
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(b))
 	if err != nil {
 		return "", err
 	}
@@ -79,27 +472,34 @@ func callOpenAIChat(ctx context.Context, hc *http.Client, apiKey, model, systemP
 	}
 	defer resp.Body.Close()
 
-	var out openAIChatResponse
-	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
-		return "", err
+	raw, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("读取响应失败: %w", err)
 	}
 
+	var out openAIChatResponse
+	_ = json.Unmarshal(raw, &out) // 容错:即便非 JSON 也不影响后续错误展示
+
 	if resp.StatusCode >= 300 {
+		// 优先使用返回的 error.message;否则附带状态码与截断的 body
 		if out.Error != nil && out.Error.Message != "" {
-			return "", fmt.Errorf("openai error: %s", out.Error.Message)
+			return "", fmt.Errorf("%s error: %s (status=%d)", provider, out.Error.Message, resp.StatusCode)
 		}
-		return "", fmt.Errorf("openai request failed: status %d", resp.StatusCode)
+		snippet := string(raw)
+		if len(snippet) > 500 {
+			snippet = snippet[:500] + "..."
+		}
+		return "", fmt.Errorf("%s request failed: status=%d, body=%s", provider, resp.StatusCode, snippet)
 	}
 
 	if len(out.Choices) == 0 {
-		return "", fmt.Errorf("openai 返回为空")
+		return "", fmt.Errorf("%s 返回为空(status=%d)", provider, resp.StatusCode)
 	}
 
 	return out.Choices[0].Message.Content, nil
 }
 
 // 请求/响应结构
-
 type openAIChatRequest struct {
 	Model    string          `json:"model"`
 	Messages []openAIMessage `json:"messages"`

+ 7 - 2
service/internal/modules/routers.go

@@ -62,8 +62,13 @@ func RegisterRoutes(engine *gin.Engine, storageManager interface{}, taskManager
 	}
 	dataQuery.RegisterRoutes(engine, dataSvc)
 
-	// 4) AI Chat 模块(通过 OpenAI/AI 配置)
-	aiSvcInst := aiSvc.NewAIService(aiCfg)
+	// 4) AI Chat 模块(开关来自 TOML,配置来自存储)
+	var aiSvcInst *aiSvc.AIService
+	if aiCfg.Enable {
+		if sm, ok := storageManager.(storagemgr.StorageInterface); ok {
+			aiSvcInst = aiSvc.NewAIService(aiCfg.Enable, sm, taskManager)
+		}
+	}
 	aiChat.RegisterRoutes(engine, aiSvcInst)
 
 	// 3) SQL 编辑器模块(sql_editor)

+ 6 - 4
service/service.go

@@ -18,9 +18,11 @@ func InitBootstrap(configPath string) (*bootstrap.App, error) {
 	// srv.Setup()
 	srv := bootstrap.NewServer(app)
 	srv.Setup()
-	// 启动服务器
-	if err := srv.Start(); err != nil {
-		log.Fatalf("服务器启动失败: %v", err)
-	}
+	// 启动服务器(异步),让 main 继续进入 app.Run 以响应 Ctrl+C 信号
+	go func() {
+		if err := srv.Start(); err != nil {
+			log.Fatalf("服务器启动失败: %v", err)
+		}
+	}()
 	return app, nil
 }