feat(tool-calling): 统一工具调用结果的头部样式并添加写文件工具
- 为WebSearchTool、KnowledgeGraphTool、TodoListTool和TaskTool添加统一的sep-header样式 - 新增WriteFileTool组件用于显示写文件工具调用结果 - 在ToolCallRenderer中集成WriteFileTool组件 - 在BaseToolCall中定义统一的sep-header样式
This commit is contained in:
parent
2b315f19ab
commit
0d87c1755e
@ -273,12 +273,43 @@ const formatResultData = (data) => {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
|
||||
:deep(.sep-header) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.keywords) {
|
||||
color: var(--main-700);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.note) {
|
||||
font-weight: 600;
|
||||
color: var(--main-700);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.separator) {
|
||||
color: var(--gray-300);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.description) {
|
||||
color: var(--gray-600);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:deep(.tag) {
|
||||
font-size: 12px;
|
||||
color: var(--gray-600);
|
||||
|
||||
@ -24,6 +24,9 @@
|
||||
<!-- 任务分配 -->
|
||||
<TaskTool v-else-if="isTaskResult" :tool-call="toolCall" />
|
||||
|
||||
<!-- 写文件 -->
|
||||
<WriteFileTool v-else-if="isWriteFileResult" :tool-call="toolCall" />
|
||||
|
||||
<!-- 默认展示 -->
|
||||
<BaseToolCall v-else :tool-call="toolCall" />
|
||||
</template>
|
||||
@ -40,6 +43,7 @@ import CalculatorTool from './tools/CalculatorTool.vue';
|
||||
import TodoListTool from './tools/TodoListTool.vue';
|
||||
import ImageTool from './tools/ImageTool.vue';
|
||||
import TaskTool from './tools/TaskTool.vue';
|
||||
import WriteFileTool from './tools/WriteFileTool.vue';
|
||||
|
||||
const props = defineProps({
|
||||
toolCall: {
|
||||
@ -121,6 +125,10 @@ const isImageResult = computed(() => {
|
||||
return data && typeof data === 'string' && data.startsWith('http');
|
||||
});
|
||||
|
||||
const isWriteFileResult = computed(() => {
|
||||
return toolName.value === 'write_file';
|
||||
});
|
||||
|
||||
// 处理知识图谱刷新
|
||||
const graphToolCallRef = ref(null);
|
||||
const refreshGraph = () => {
|
||||
|
||||
@ -8,4 +8,5 @@ export { default as KnowledgeBaseTool } from './tools/KnowledgeBaseTool.vue'
|
||||
export { default as KnowledgeGraphTool } from './tools/KnowledgeGraphTool.vue'
|
||||
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 ImageTool } from './tools/ImageTool.vue'
|
||||
export { default as WriteFileTool } from './tools/WriteFileTool.vue'
|
||||
@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall">
|
||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">knowledgegraph</span>
|
||||
<span class="separator" v-if="query">|</span>
|
||||
<span class="description">{{ query }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #result="{ resultContent }">
|
||||
<div class="knowledge-graph-result">
|
||||
<div class="result-summary">
|
||||
@ -57,6 +64,24 @@ const graphContainerRef = ref(null)
|
||||
const isVisible = ref(false)
|
||||
const isRefreshing = ref(false)
|
||||
|
||||
const query = computed(() => {
|
||||
const args = props.toolCall.args || props.toolCall.function?.arguments;
|
||||
if (!args) return '';
|
||||
let parsedArgs = args;
|
||||
if (typeof args === 'string') {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
// Try common keys for KG queries
|
||||
if (typeof parsedArgs === 'object') {
|
||||
return parsedArgs.query || parsedArgs.keywords || parsedArgs.q || parsedArgs.entities || '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 计算属性:解析图谱数据
|
||||
const graphData = computed(() => {
|
||||
const data = parseData(props.toolCall.tool_call_result?.content);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||
<template #header>
|
||||
<div class="task-header">
|
||||
<div class="sep-header">
|
||||
<span class="subagent">{{ subagentType }}</span>
|
||||
<span class="separator" v-if="shortDescription">|</span>
|
||||
<span class="description" v-if="shortDescription">{{ shortDescription }}</span>
|
||||
@ -59,7 +59,7 @@ const shortDescription = computed(() => {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.task-header {
|
||||
.sep-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@ -74,19 +74,6 @@ const shortDescription = computed(() => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--gray-300);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--gray-600);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.task-result {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" hide-params>
|
||||
<template #header-success="{ resultContent }">
|
||||
<span v-if="todoListData(resultContent).length > 0" v-html="getHeaderDisplayContent(resultContent)"></span>
|
||||
<span v-else>
|
||||
待办事项工具执行完成
|
||||
</span>
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">todo</span>
|
||||
<span class="separator" v-if="query">|</span>
|
||||
<span class="description">{{ query }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #result="{ resultContent }">
|
||||
<div class="todo-list-result">
|
||||
@ -33,6 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import BaseToolCall from '../BaseToolCall.vue';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
@ -49,6 +51,43 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const query = computed(() => {
|
||||
// 1. Try to get status from result content (Priority)
|
||||
const content = props.toolCall.tool_call_result?.content;
|
||||
if (content) {
|
||||
const list = todoListData(content);
|
||||
if (list && list.length > 0) {
|
||||
// 1. In Progress
|
||||
const inProgress = list.find(item => item.status === 'in_progress');
|
||||
if (inProgress) return `进行中: ${inProgress.content}`;
|
||||
|
||||
// 2. Pending
|
||||
const pending = list.find(item => item.status === 'pending');
|
||||
if (pending) return `待处理: ${pending.content}`;
|
||||
|
||||
// 3. Last item fallback
|
||||
const last = list[list.length - 1];
|
||||
return `更新: ${last.content}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to args
|
||||
const args = props.toolCall.args || props.toolCall.function?.arguments;
|
||||
if (!args) return '';
|
||||
let parsedArgs = args;
|
||||
if (typeof args === 'string') {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if (typeof parsedArgs === 'object') {
|
||||
return parsedArgs.content || parsedArgs.action || parsedArgs.todo || '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const parseData = (content) => {
|
||||
if (typeof content === 'string') {
|
||||
try {
|
||||
@ -97,23 +136,6 @@ const todoListData = (content) => {
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const getHeaderDisplayContent = (content) => {
|
||||
const list = todoListData(content);
|
||||
if (!list || list.length === 0) return '';
|
||||
|
||||
// 1. 显示 in_progress
|
||||
const inProgress = list.find(item => item.status === 'in_progress');
|
||||
if (inProgress) return `进行中:<span class="keywords">${inProgress.content}</span>`;
|
||||
|
||||
// 2. 显示第一个 pending
|
||||
const pending = list.find(item => item.status === 'pending');
|
||||
if (pending) return `待处理:<span class="keywords">${pending.content}</span>`;
|
||||
|
||||
// 3. 显示最后一个 (fallback)
|
||||
const last = list[list.length - 1];
|
||||
return `更新:<span class="keywords">${last.content}</span>`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||
<template #header>
|
||||
查询:
|
||||
<span class="keywords">"{{ query }}"</span>
|
||||
<div class="sep-header">
|
||||
<span class="note">websearch</span>
|
||||
<span class="separator" v-if="query">|</span>
|
||||
<span class="description">"{{ query }}"</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #result="{ resultContent }">
|
||||
<div class="web-search-result">
|
||||
|
||||
50
web/src/components/ToolCallingResult/tools/WriteFileTool.vue
Normal file
50
web/src/components/ToolCallingResult/tools/WriteFileTool.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall">
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">write_file</span>
|
||||
<span class="separator" v-if="filePath">|</span>
|
||||
<span class="description">{{ filePath }} <span v-if="lineCount"> ({{ lineCount }} lines)</span></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #result>
|
||||
</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 content = computed(() => parsedArgs.value.content || '');
|
||||
const lineCount = computed(() => {
|
||||
if (!content.value) return 0;
|
||||
return String(content.value).split('\n').length;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user