feat: 添加更多工具解析组件

This commit is contained in:
Wenjie Zhang 2026-02-03 14:03:57 +08:00
parent d47a66ea88
commit b589521775
10 changed files with 307 additions and 9 deletions

View File

@ -126,7 +126,7 @@
</button>
</div>
<div v-if="!attachmentCount" class="empty">
<p>暂无附件</p>
<p>暂无附件支持 txt/md/docx/html 格式 5 MB</p>
<a-button type="primary" @click="triggerUpload" :loading="isUploading">上传附件</a-button>
</div>
<div v-else class="file-tree-container attachment-tree">
@ -827,7 +827,7 @@ const stopResize = () => {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px;
padding: 6px 12px;
border-radius: 8px;
border: 1px solid var(--gray-150);
transition: all 0.15s ease;

View File

@ -305,11 +305,24 @@ const formatResultData = (data) => {
:deep(.tag) {
font-size: 12px;
color: var(--gray-600);
background-color: var(--gray-100);
padding: 0px 4px;
color: var(--gray-800);
background-color: var(--gray-50);
padding: 4px 4px;
border-radius: 4px;
margin-left: 8px;
&.tag-yes {
color: var(--main-500);
}
&.success {
color: var(--color-success-500);
background-color: var(--color-success-50);
}
&.error {
color: var(--color-error-500);
background-color: var(--color-error-50);
}
}
}
}

View File

@ -23,6 +23,21 @@
<!-- 写文件 -->
<WriteFileTool v-else-if="isWriteFileResult" :tool-call="toolCall" />
<!-- 读文件 -->
<ReadFileTool v-else-if="isReadFileResult" :tool-call="toolCall" />
<!-- 列目录 -->
<ListDirectoryTool v-else-if="isListDirectoryResult" :tool-call="toolCall" />
<!-- 搜索文件内容 -->
<SearchFileContentTool v-else-if="isSearchFileContentResult" :tool-call="toolCall" />
<!-- Glob 搜索 -->
<GlobTool v-else-if="isGlobResult" :tool-call="toolCall" />
<!-- 编辑文件 -->
<EditFileTool v-else-if="isEditFileResult" :tool-call="toolCall" />
<!-- 默认展示 -->
<BaseToolCall v-else :tool-call="toolCall" />
</template>
@ -41,6 +56,11 @@ import TodoListTool from './tools/TodoListTool.vue'
import ImageTool from './tools/ImageTool.vue'
import TaskTool from './tools/TaskTool.vue'
import WriteFileTool from './tools/WriteFileTool.vue'
import ReadFileTool from './tools/ReadFileTool.vue'
import ListDirectoryTool from './tools/ListDirectoryTool.vue'
import SearchFileContentTool from './tools/SearchFileContentTool.vue'
import GlobTool from './tools/GlobTool.vue'
import EditFileTool from './tools/EditFileTool.vue'
const props = defineProps({
toolCall: {
@ -130,6 +150,26 @@ const isWriteFileResult = computed(() => {
return toolName.value === 'write_file'
})
const isReadFileResult = computed(() => {
return toolName.value === 'read_file'
})
const isListDirectoryResult = computed(() => {
return toolName.value === 'list_directory' || toolName.value === 'ls'
})
const isSearchFileContentResult = computed(() => {
return toolName.value === 'search_file_content'
})
const isGlobResult = computed(() => {
return toolName.value === 'glob'
})
const isEditFileResult = computed(() => {
return toolName.value === 'edit_file' || toolName.value === 'replace'
})
//
const graphToolCallRef = ref(null)
const refreshGraph = () => {

View File

@ -10,3 +10,8 @@ export { default as CalculatorTool } from './tools/CalculatorTool.vue'
export { default as TodoListTool } from './tools/TodoListTool.vue'
export { default as ImageTool } from './tools/ImageTool.vue'
export { default as WriteFileTool } from './tools/WriteFileTool.vue'
export { default as ReadFileTool } from './tools/ReadFileTool.vue'
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'

View File

@ -0,0 +1,55 @@
<template>
<BaseToolCall :tool-call="toolCall">
<template #header>
<div class="sep-header">
<span class="note">{{ toolCallName }}</span>
<span class="separator" v-if="filePath">|</span>
<span class="description">
{{ filePath }}
<span class="tag success" v-if="addedLines > 0">+{{ addedLines }}</span>
<span class="tag error" v-if="removedLines > 0">-{{ removedLines }}</span>
</span>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import { computed } from 'vue'
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const toolCallName = computed(() => props.toolCall.name || props.toolCall.function?.name || 'edit_file')
const parsedArgs = computed(() => {
const args = props.toolCall.args || props.toolCall.function?.arguments
if (!args) return {}
if (typeof args === 'object') return args
try {
return JSON.parse(args)
} catch (e) {
return {}
}
})
const filePath = computed(() => {
const path = parsedArgs.value.file_path || ''
return path.startsWith('/') ? path.slice(1) : path
})
const addedLines = computed(() => {
const newStr = parsedArgs.value.new_string || ''
return newStr ? String(newStr).split('\n').length : 0
})
const removedLines = computed(() => {
const oldStr = parsedArgs.value.old_string || ''
return oldStr ? String(oldStr).split('\n').length : 0
})
</script>

View File

@ -0,0 +1,38 @@
<template>
<BaseToolCall :tool-call="toolCall">
<template #header>
<div class="sep-header">
<span class="note">glob</span>
<span class="separator" v-if="pattern">|</span>
<span class="description">{{ pattern }}</span>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import { computed } from 'vue'
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const parsedArgs = computed(() => {
const args = props.toolCall.args || props.toolCall.function?.arguments
if (!args) return {}
if (typeof args === 'object') return args
try {
return JSON.parse(args)
} catch (e) {
return {}
}
})
const pattern = computed(() => parsedArgs.value.pattern || '')
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,44 @@
<template>
<BaseToolCall :tool-call="toolCall" :hide-params="true">
<template #header>
<div class="sep-header">
<span class="note">{{ toolCallName }}</span>
<span class="separator" v-if="dirPath">|</span>
<span class="description">{{ dirPath }}</span>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import { computed } from 'vue'
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const toolCallName = computed(
() => props.toolCall.name || props.toolCall.function?.name || 'list_directory'
)
const parsedArgs = computed(() => {
const args = props.toolCall.args || props.toolCall.function?.arguments
if (!args) return {}
if (typeof args === 'object') return args
try {
return JSON.parse(args)
} catch (e) {
return {}
}
})
const dirPath = computed(() => {
return parsedArgs.value.dir_path || parsedArgs.value.path || ''
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,62 @@
<template>
<BaseToolCall :tool-call="toolCall">
<template #header>
<div class="sep-header">
<span class="note">read_file</span>
<span class="separator" v-if="filePath">|</span>
<span class="description">
{{ filePath }}
<span class="tag" v-if="lineRange">{{ lineRange }}</span>
</span>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import { computed } from 'vue'
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const parsedArgs = computed(() => {
const args = props.toolCall.args || props.toolCall.function?.arguments
if (!args) return {}
if (typeof args === 'object') return args
try {
return JSON.parse(args)
} catch (e) {
return {}
}
})
const filePath = computed(() => {
const path = parsedArgs.value.file_path || ''
return path.startsWith('/') ? path.slice(1) : path
})
const lineRange = computed(() => {
const offset = parsedArgs.value.offset
const limit = parsedArgs.value.limit
if (offset !== undefined && limit !== undefined) {
return `Lines ${offset}-${Number(offset) + Number(limit)}`
} else if (limit !== undefined) {
return `First ${limit} lines`
}
return ''
})
</script>
<style lang="less" scoped>
.sep-header {
.tag {
color: var(--color-primary-600);
background-color: var(--color-primary-50);
}
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<BaseToolCall :tool-call="toolCall">
<template #header>
<div class="sep-header">
<span class="note">search_file_content</span>
<span class="separator" v-if="pattern">|</span>
<span class="keywords">{{ pattern }}</span>
<span class="separator" v-if="dirPath">|</span>
<span class="description">{{ dirPath }}</span>
</div>
</template>
</BaseToolCall>
</template>
<script setup>
import { computed } from 'vue'
import BaseToolCall from '../BaseToolCall.vue'
const props = defineProps({
toolCall: {
type: Object,
required: true
}
})
const parsedArgs = computed(() => {
const args = props.toolCall.args || props.toolCall.function?.arguments
if (!args) return {}
if (typeof args === 'object') return args
try {
return JSON.parse(args)
} catch (e) {
return {}
}
})
const pattern = computed(() => parsedArgs.value.pattern || '')
const dirPath = computed(() => parsedArgs.value.dir_path || '')
</script>
<style lang="less" scoped></style>

View File

@ -147,17 +147,17 @@ const todoListData = (content) => {
.todo-list {
display: flex;
flex-direction: column;
gap: 8px;
gap: 2px;
}
.todo-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
background: var(--gray-25);
padding: 4px 8px;
// background: var(--gray-10);
border-radius: 6px;
border: 1px solid var(--gray-150);
// border: 1px solid var(--gray-150);
.todo-status {
flex-shrink: 0;