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;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
|
||||||
|
:deep(.sep-header) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.keywords) {
|
:deep(.keywords) {
|
||||||
color: var(--main-700);
|
color: var(--main-700);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
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) {
|
:deep(.tag) {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--gray-600);
|
color: var(--gray-600);
|
||||||
|
|||||||
@ -24,6 +24,9 @@
|
|||||||
<!-- 任务分配 -->
|
<!-- 任务分配 -->
|
||||||
<TaskTool v-else-if="isTaskResult" :tool-call="toolCall" />
|
<TaskTool v-else-if="isTaskResult" :tool-call="toolCall" />
|
||||||
|
|
||||||
|
<!-- 写文件 -->
|
||||||
|
<WriteFileTool v-else-if="isWriteFileResult" :tool-call="toolCall" />
|
||||||
|
|
||||||
<!-- 默认展示 -->
|
<!-- 默认展示 -->
|
||||||
<BaseToolCall v-else :tool-call="toolCall" />
|
<BaseToolCall v-else :tool-call="toolCall" />
|
||||||
</template>
|
</template>
|
||||||
@ -40,6 +43,7 @@ import CalculatorTool from './tools/CalculatorTool.vue';
|
|||||||
import TodoListTool from './tools/TodoListTool.vue';
|
import TodoListTool from './tools/TodoListTool.vue';
|
||||||
import ImageTool from './tools/ImageTool.vue';
|
import ImageTool from './tools/ImageTool.vue';
|
||||||
import TaskTool from './tools/TaskTool.vue';
|
import TaskTool from './tools/TaskTool.vue';
|
||||||
|
import WriteFileTool from './tools/WriteFileTool.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
toolCall: {
|
toolCall: {
|
||||||
@ -121,6 +125,10 @@ const isImageResult = computed(() => {
|
|||||||
return data && typeof data === 'string' && data.startsWith('http');
|
return data && typeof data === 'string' && data.startsWith('http');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isWriteFileResult = computed(() => {
|
||||||
|
return toolName.value === 'write_file';
|
||||||
|
});
|
||||||
|
|
||||||
// 处理知识图谱刷新
|
// 处理知识图谱刷新
|
||||||
const graphToolCallRef = ref(null);
|
const graphToolCallRef = ref(null);
|
||||||
const refreshGraph = () => {
|
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 KnowledgeGraphTool } from './tools/KnowledgeGraphTool.vue'
|
||||||
export { default as CalculatorTool } from './tools/CalculatorTool.vue'
|
export { default as CalculatorTool } from './tools/CalculatorTool.vue'
|
||||||
export { default as TodoListTool } from './tools/TodoListTool.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>
|
<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 }">
|
<template #result="{ resultContent }">
|
||||||
<div class="knowledge-graph-result">
|
<div class="knowledge-graph-result">
|
||||||
<div class="result-summary">
|
<div class="result-summary">
|
||||||
@ -57,6 +64,24 @@ const graphContainerRef = ref(null)
|
|||||||
const isVisible = ref(false)
|
const isVisible = ref(false)
|
||||||
const isRefreshing = 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 graphData = computed(() => {
|
||||||
const data = parseData(props.toolCall.tool_call_result?.content);
|
const data = parseData(props.toolCall.tool_call_result?.content);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="task-header">
|
<div class="sep-header">
|
||||||
<span class="subagent">{{ subagentType }}</span>
|
<span class="subagent">{{ subagentType }}</span>
|
||||||
<span class="separator" v-if="shortDescription">|</span>
|
<span class="separator" v-if="shortDescription">|</span>
|
||||||
<span class="description" v-if="shortDescription">{{ shortDescription }}</span>
|
<span class="description" v-if="shortDescription">{{ shortDescription }}</span>
|
||||||
@ -59,7 +59,7 @@ const shortDescription = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.task-header {
|
.sep-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@ -74,19 +74,6 @@ const shortDescription = computed(() => {
|
|||||||
flex-shrink: 0;
|
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 {
|
.task-result {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseToolCall :tool-call="toolCall" hide-params>
|
<BaseToolCall :tool-call="toolCall" hide-params>
|
||||||
<template #header-success="{ resultContent }">
|
<template #header>
|
||||||
<span v-if="todoListData(resultContent).length > 0" v-html="getHeaderDisplayContent(resultContent)"></span>
|
<div class="sep-header">
|
||||||
<span v-else>
|
<span class="note">todo</span>
|
||||||
待办事项工具执行完成
|
<span class="separator" v-if="query">|</span>
|
||||||
</span>
|
<span class="description">{{ query }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #result="{ resultContent }">
|
<template #result="{ resultContent }">
|
||||||
<div class="todo-list-result">
|
<div class="todo-list-result">
|
||||||
@ -33,6 +34,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
import BaseToolCall from '../BaseToolCall.vue';
|
import BaseToolCall from '../BaseToolCall.vue';
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined,
|
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) => {
|
const parseData = (content) => {
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
try {
|
try {
|
||||||
@ -97,23 +136,6 @@ const todoListData = (content) => {
|
|||||||
}
|
}
|
||||||
return [];
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||||
<template #header>
|
<template #header>
|
||||||
查询:
|
<div class="sep-header">
|
||||||
<span class="keywords">"{{ query }}"</span>
|
<span class="note">websearch</span>
|
||||||
|
<span class="separator" v-if="query">|</span>
|
||||||
|
<span class="description">"{{ query }}"</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #result="{ resultContent }">
|
<template #result="{ resultContent }">
|
||||||
<div class="web-search-result">
|
<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