feat(tool): 优化 MySQL 工具命名并添加前端渲染组件

- 将工具 name_or_callable 从中文改为英文 (mysql_query, mysql_describe_table, mysql_list_tables)
- 添加 MySQL 工具结果的前端渲染组件,提升用户体验
This commit is contained in:
Wenjie Zhang 2026-02-15 21:57:41 +08:00
parent d66aa64436
commit ed849a2e5c
6 changed files with 302 additions and 6 deletions

View File

@ -52,9 +52,9 @@ class TableListModel(BaseModel):
pass
@tool(name_or_callable="查询表名及说明", args_schema=TableListModel)
@tool(name_or_callable="mysql_list_tables", args_schema=TableListModel)
def mysql_list_tables() -> str:
"""获取数据库中的所有表名
"""【查询表名及说明】获取数据库中的所有表名
这个工具用来列出当前数据库中所有的表名帮助你了解数据库的结构
"""
@ -107,9 +107,9 @@ class TableDescribeModel(BaseModel):
table_name: str = Field(description="要查询的表名", example="users")
@tool(name_or_callable="描述表", args_schema=TableDescribeModel)
@tool(name_or_callable="mysql_describe_table", args_schema=TableDescribeModel)
def mysql_describe_table(table_name: Annotated[str, "要查询结构的表名"]) -> str:
"""获取指定表的详细结构信息
"""【描述表】获取指定表的详细结构信息
这个工具用来查看表的字段信息数据类型是否允许NULL默认值键类型等
帮助你了解表的结构以便编写正确的SQL查询
@ -203,12 +203,12 @@ class QueryModel(BaseModel):
timeout: int | None = Field(default=60, description="查询超时时间默认60秒最大600秒", ge=1, le=600)
@tool(name_or_callable="执行 SQL 查询", args_schema=QueryModel)
@tool(name_or_callable="mysql_query", args_schema=QueryModel)
def mysql_query(
sql: Annotated[str, "要执行的SQL查询语句只能是SELECT语句"],
timeout: Annotated[int | None, "查询超时时间默认60秒最大600秒"] = 60,
) -> str:
"""执行只读的SQL查询语句
"""【执行 SQL 查询】执行只读的SQL查询语句
这个工具用来执行SQL查询并返回结果支持复杂的SELECT查询包括JOINGROUP BY等
注意只能执行查询操作不能修改数据

View File

@ -38,6 +38,15 @@
<!-- 编辑文件 -->
<EditFileTool v-else-if="isEditFileResult" :tool-call="toolCall" />
<!-- MySQL 查询 -->
<MysqlQueryTool v-else-if="isMysqlQueryResult" :tool-call="toolCall" />
<!-- MySQL 描述表 -->
<MysqlDescribeTableTool v-else-if="isMysqlDescribeTableResult" :tool-call="toolCall" />
<!-- MySQL 列出表 -->
<MysqlListTablesTool v-else-if="isMysqlListTablesResult" :tool-call="toolCall" />
<!-- 默认展示 -->
<BaseToolCall v-else :tool-call="toolCall" />
</template>
@ -61,6 +70,9 @@ import ListDirectoryTool from './tools/ListDirectoryTool.vue'
import SearchFileContentTool from './tools/SearchFileContentTool.vue'
import GlobTool from './tools/GlobTool.vue'
import EditFileTool from './tools/EditFileTool.vue'
import MysqlQueryTool from './tools/MysqlQueryTool.vue'
import MysqlDescribeTableTool from './tools/MysqlDescribeTableTool.vue'
import MysqlListTablesTool from './tools/MysqlListTablesTool.vue'
const props = defineProps({
toolCall: {
@ -170,6 +182,18 @@ const isEditFileResult = computed(() => {
return toolName.value === 'edit_file' || toolName.value === 'replace'
})
const isMysqlQueryResult = computed(() => {
return toolName.value === 'mysql_query'
})
const isMysqlDescribeTableResult = computed(() => {
return toolName.value === 'mysql_describe_table'
})
const isMysqlListTablesResult = computed(() => {
return toolName.value === 'mysql_list_tables'
})
//
const graphToolCallRef = ref(null)
const refreshGraph = () => {

View File

@ -15,3 +15,6 @@ export { default as ListDirectoryTool } from './tools/ListDirectoryTool.vue'
export { default as SearchFileContentTool } from './tools/SearchFileContentTool.vue'
export { default as GlobTool } from './tools/GlobTool.vue'
export { default as EditFileTool } from './tools/EditFileTool.vue'
export { default as MysqlQueryTool } from './tools/MysqlQueryTool.vue'
export { default as MysqlDescribeTableTool } from './tools/MysqlDescribeTableTool.vue'
export { default as MysqlListTablesTool } from './tools/MysqlListTablesTool.vue'

View File

@ -0,0 +1,83 @@
<template>
<BaseToolCall :tool-call="toolCall" :default-expanded="true" :hide-params="true">
<template #header-success>
<span class="sep-header">
<span class="keywords">描述表结构</span>
<span class="description code">{{ extractTableName(toolCall.args || toolCall.function?.arguments) }}</span>
</span>
</template>
<template #result="{ resultContent }">
<div class="mysql-result">
<pre class="result-text">{{ formatResult(resultContent) }}</pre>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const formatResult = (content) => {
if (!content) return ''
if (typeof content === 'string') {
try {
const parsed = JSON.parse(content)
return JSON.stringify(parsed, null, 2)
} catch {
return content
}
}
if (typeof content === 'object') {
return JSON.stringify(content, null, 2)
}
return String(content)
}
const extractTableName = (args) => {
if (!args) return ''
let parsedArgs = args
if (typeof args === 'string') {
try {
parsedArgs = JSON.parse(args)
} catch {
return args
}
}
return parsedArgs?.table_name || ''
}
</script>
<style lang="less" scoped>
.mysql-result {
border-radius: 8px;
padding: 12px;
.result-text {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: var(--gray-700);
white-space: pre-wrap;
word-break: break-word;
max-height: 400px;
overflow-y: auto;
background: var(--gray-50);
padding: 10px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<BaseToolCall :tool-call="toolCall" :default-expanded="true" :hide-params="true">
<template #header-success>
<span class="sep-header">
<span class="keywords">列出数据库表</span>
</span>
</template>
<template #result="{ resultContent }">
<div class="mysql-result">
<pre class="result-text">{{ formatResult(resultContent) }}</pre>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const formatResult = (content) => {
if (!content) return ''
if (typeof content === 'string') {
try {
const parsed = JSON.parse(content)
return JSON.stringify(parsed, null, 2)
} catch {
return content
}
}
if (typeof content === 'object') {
return JSON.stringify(content, null, 2)
}
return String(content)
}
</script>
<style lang="less" scoped>
.mysql-result {
border-radius: 8px;
padding: 12px;
.result-text {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: var(--gray-700);
white-space: pre-wrap;
word-break: break-word;
max-height: 400px;
overflow-y: auto;
background: var(--gray-50);
padding: 10px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<BaseToolCall :tool-call="toolCall" :default-expanded="true">
<template #header-success>
<span class="sep-header">
<span class="keywords">执行SQL查询</span>
<span class="description">{{ truncateSql(extractSql(toolCall.args || toolCall.function?.arguments)) }}</span>
</span>
</template>
<template #params="{ args }">
<div class="mysql-params">
<pre class="sql-text">{{ extractSql(args) }}</pre>
</div>
</template>
<template #result="{ resultContent }">
<div class="mysql-result">
<pre class="result-text">{{ formatResult(resultContent) }}</pre>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const formatResult = (content) => {
if (!content) return ''
// JSON
if (typeof content === 'string') {
try {
const parsed = JSON.parse(content)
return JSON.stringify(parsed, null, 2)
} catch {
return content
}
}
//
if (typeof content === 'object') {
return JSON.stringify(content, null, 2)
}
return String(content)
}
const extractSql = (args) => {
if (!args) return ''
// args
let parsedArgs = args
if (typeof args === 'string') {
try {
parsedArgs = JSON.parse(args)
} catch {
return args
}
}
// sql
const sql = parsedArgs?.sql || parsedArgs?.query
return sql || JSON.stringify(parsedArgs, null, 2)
}
const truncateSql = (sql, maxLength = 50) => {
if (!sql) return ''
//
const singleLine = sql.replace(/\s+/g, ' ').trim()
if (singleLine.length <= maxLength) return singleLine
return singleLine.slice(0, maxLength) + '...'
}
</script>
<style lang="less" scoped>
.mysql-params {
.sql-text {
margin: 0;
font-size: 11px;
line-height: 1.4;
color: var(--gray-800);
white-space: pre-wrap;
word-break: break-word;
padding: 6px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
max-height: 200px;
overflow-y: auto;
}
}
.mysql-result {
// background: var(--gray-0);
border-radius: 8px;
padding: 12px;
.result-text {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: var(--gray-700);
white-space: pre-wrap;
word-break: break-word;
max-height: 400px;
overflow-y: auto;
background: var(--gray-50);
padding: 10px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
}
</style>