支持一次对话同时调用多个工具

This commit is contained in:
锦恢 2025-05-08 22:28:44 +08:00
parent da482e26a9
commit 4c0566b470
9 changed files with 289 additions and 214 deletions

View File

@ -1,6 +1,5 @@
# Change Log # Change Log
## [main] 0.0.8 ## [main] 0.0.8
- 大模型 API 测试时更加完整的报错 - 大模型 API 测试时更加完整的报错
- 修复 0.0.7 引入的bug修改对话无法发出 - 修复 0.0.7 引入的bug修改对话无法发出
@ -8,6 +7,7 @@
- 修复 bug富文本编辑器发送前缀为空的字符会全部为空 - 修复 bug富文本编辑器发送前缀为空的字符会全部为空
- 修复 bug流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败 - 修复 bug流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败
- 修复 bug大模型返回大量重复错误信息 - 修复 bug大模型返回大量重复错误信息
- 新特性:支持一次对话同时调用多个工具
## [main] 0.0.7 ## [main] 0.0.7
- 优化页面布局,使得调试窗口可以显示更多内容 - 优化页面布局,使得调试窗口可以显示更多内容

View File

@ -131,6 +131,10 @@ a {
height: 5px; height: 5px;
} }
.tool-arguments .openmcp-code-block pre code::-webkit-scrollbar {
background: transparent !important;
}
.tool-arguments .openmcp-code-block pre code { .tool-arguments .openmcp-code-block pre code {
background: transparent !important; background: transparent !important;
} }

View File

@ -27,6 +27,7 @@ export interface IExtraInfo {
export interface ToolMessage { export interface ToolMessage {
role: 'tool'; role: 'tool';
index: number;
content: ToolCallContent[]; content: ToolCallContent[];
tool_call_id?: string tool_call_id?: string
name?: string // 工具名称,当 role 为 tool name?: string // 工具名称,当 role 为 tool
@ -95,15 +96,24 @@ export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
export const allTools = ref<ToolItem[]>([]); export const allTools = ref<ToolItem[]>([]);
export interface IRenderMessage { export interface ICommonRenderMessage {
role: 'user' | 'assistant/content' | 'assistant/tool_calls' | 'tool'; role: 'user' | 'assistant/content';
content: string; content: string;
toolResult?: ToolCallContent[];
tool_calls?: ToolCall[];
showJson?: Ref<boolean>; showJson?: Ref<boolean>;
extraInfo: IExtraInfo; extraInfo: IExtraInfo;
} }
export interface IToolRenderMessage {
role: 'assistant/tool_calls';
content: string;
toolResults: ToolCallContent[][];
tool_calls: ToolCall[];
showJson?: Ref<boolean>;
extraInfo: IExtraInfo;
}
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) { export function getToolSchema(enableTools: EnableToolItem[]) {
const toolsSchema = []; const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) { for (let i = 0; i < enableTools.length; i++) {

View File

@ -65,6 +65,23 @@ const chatContext = inject('chatContext') as any;
chatContext.handleSend = handleSend; chatContext.handleSend = handleSend;
function clearErrorMessage(errorMessage: string) {
try {
const errorObject = JSON.parse(errorMessage);
if (errorObject.error) {
return errorObject.error;
}
if (errorObject.message) {
return errorObject.message;
}
if (errorObject.msg) {
return errorObject.msg;
}
} catch (error) {
return errorMessage;
}
}
function handleSend(newMessage?: string) { function handleSend(newMessage?: string) {
// //
const userMessage = newMessage || userInput.value; const userMessage = newMessage || userInput.value;
@ -79,13 +96,16 @@ function handleSend(newMessage?: string) {
loop = new TaskLoop(streamingContent, streamingToolCalls); loop = new TaskLoop(streamingContent, streamingToolCalls);
loop.registerOnError((error) => { loop.registerOnError((error) => {
console.log('error.msg');
ElMessage.error(error.msg); console.log(error.msg);
const errorMessage = clearErrorMessage(error.msg);
ElMessage.error(errorMessage);
if (error.state === MessageState.ReceiveChunkError) { if (error.state === MessageState.ReceiveChunkError) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'assistant', role: 'assistant',
content: error.msg, content: errorMessage,
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: error.state, state: error.state,

View File

@ -0,0 +1,81 @@
import { ToolCallResponse } from "@/hook/type";
import { callTool } from "../../tool/tools";
import { MessageState, ToolCall } from "../chat-box/chat";
export async function handleToolCalls(toolCall: ToolCall) {
// 反序列化 streaming 来的参数字符串
const toolName = toolCall.function.name;
const argsResult = deserializeToolCallResponse(toolCall.function.arguments);
if (argsResult.error) {
return {
content: [{
type: 'error',
text: parseErrorObject(argsResult.error)
}],
state: MessageState.ParseJsonError
};
}
const toolArgs = argsResult.value;
// 进行调用,根据结果返回不同的值
const toolResponse = await callTool(toolName, toolArgs);
return handleToolResponse(toolResponse);
}
function deserializeToolCallResponse(toolArgs: string) {
try {
const args = JSON.parse(toolArgs);
return {
value: args,
error: undefined
}
} catch (error) {
return {
value: undefined,
error
}
}
}
function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') {
// 如果是 string说明是错误信息
console.log(toolResponse);
return {
content: [{
type: 'error',
text: toolResponse
}],
state: MessageState.ToolCall
}
} else if (!toolResponse.isError) {
return {
content: toolResponse.content,
state: MessageState.Success
};
} else {
return {
content: toolResponse.content,
state: MessageState.ToolCall
};
}
}
function parseErrorObject(error: any): string {
if (typeof error === 'string') {
return error;
} else if (typeof error === 'object') {
return JSON.stringify(error, null, 2);
} else {
return error.toString();
}
}

View File

@ -7,6 +7,7 @@ import { callTool } from "../../tool/tools";
import { llmManager, llms } from "@/views/setting/llm"; import { llmManager, llms } from "@/views/setting/llm";
import { pinkLog, redLog } from "@/views/setting/util"; import { pinkLog, redLog } from "@/views/setting/util";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { handleToolCalls } from "./handle-tool-calls";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string }; export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
@ -44,87 +45,6 @@ export class TaskLoop {
} }
private async handleToolCalls(toolCalls: ToolCall[]) {
// TODO: 调用多个工具并返回调用结果?
const toolCall = toolCalls[0];
console.log('debug toolcall');
console.log(toolCalls);
let toolName: string;
let toolArgs: Record<string, any>;
try {
toolName = toolCall.function.name;
toolArgs = JSON.parse(toolCall.function.arguments);
} catch (error) {
return {
content: [{
type: 'error',
text: this.parseErrorObject(error)
}],
state: MessageState.ParseJsonError
};
}
try {
const toolResponse = await callTool(toolName, toolArgs);
console.log(toolResponse);
if (typeof toolResponse === 'string') {
console.log(toolResponse);
return {
content: [{
type: 'error',
text: toolResponse
}],
state: MessageState.ToolCall
}
} else if (!toolResponse.isError) {
return {
content: toolResponse.content,
state: MessageState.Success
};
} else {
return {
content: toolResponse.content,
state: MessageState.ToolCall
}
}
} catch (error) {
this.onError({
state: MessageState.ToolCall,
msg: `工具调用失败: ${(error as Error).message}`
});
console.error(error);
return {
content: [{
type: 'error',
text: this.parseErrorObject(error)
}],
state: MessageState.ToolCall
}
}
}
private parseErrorObject(error: any): string {
if (typeof error === 'string') {
return error;
} else if (typeof error === 'object') {
return JSON.stringify(error, null, 2);
} else {
return error.toString();
}
}
private handleChunkDeltaContent(chunk: ChatCompletionChunk) { private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
const content = chunk.choices[0]?.delta?.content || ''; const content = chunk.choices[0]?.delta?.content || '';
if (content) { if (content) {
@ -199,12 +119,7 @@ export class TaskLoop {
}); });
}, { once: true }); }, { once: true });
console.log('register error handler');
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => { const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
console.log('enter error report');
this.onError({ this.onError({
state: MessageState.ReceiveChunkError, state: MessageState.ReceiveChunkError,
msg: data.msg || '请求模型服务时发生错误' msg: data.msg || '请求模型服务时发生错误'
@ -219,6 +134,8 @@ export class TaskLoop {
}, { once: true }); }, { once: true });
console.log(chatData);
this.bridge.postMessage({ this.bridge.postMessage({
command: 'llm/chat/completions', command: 'llm/chat/completions',
data: JSON.parse(JSON.stringify(chatData)), data: JSON.parse(JSON.stringify(chatData)),
@ -360,59 +277,58 @@ export class TaskLoop {
pinkLog('调用工具数量:' + this.streamingToolCalls.value.length); pinkLog('调用工具数量:' + this.streamingToolCalls.value.length);
const toolCallResult = await this.handleToolCalls(this.streamingToolCalls.value); for (const toolCall of this.streamingToolCalls.value || []) {
const toolCallResult = await handleToolCalls(toolCall);
console.log('toolCallResult', toolCallResult);
if (toolCallResult.state === MessageState.ParseJsonError) {
if (toolCallResult.state === MessageState.ParseJsonError) { // 如果是因为解析 JSON 错误,则重新开始
// 如果是因为解析 JSON 错误,则重新开始 tabStorage.messages.pop();
tabStorage.messages.pop(); jsonParseErrorRetryCount ++;
jsonParseErrorRetryCount ++;
redLog('解析 JSON 错误 ' + toolCall?.function?.arguments);
redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments);
// 如果因为 JSON 错误而失败太多,就只能中断了
// 如果因为 JSON 错误而失败太多,就只能中断了 if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) {
if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) { tabStorage.messages.push({
role: 'assistant',
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`,
extraInfo: {
created: Date.now(),
state: toolCallResult.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
usage: undefined
}
});
break;
}
} else if (toolCallResult.state === MessageState.Success) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'assistant', role: 'tool',
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`, index: toolCall.index || 0,
tool_call_id: toolCall.id || toolCall.function.name,
content: toolCallResult.content,
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown', serverName: llms[llmManager.currentModelIndex].id || 'unknown',
usage: undefined usage: this.completionUsage
}
});
} else if (toolCallResult.state === MessageState.ToolCall) {
tabStorage.messages.push({
role: 'tool',
index: toolCall.index || 0,
tool_call_id: toolCall.id || toolCall.function.name,
content: toolCallResult.content,
extraInfo: {
created: Date.now(),
state: toolCallResult.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
usage: this.completionUsage
} }
}); });
break;
} }
} else if (toolCallResult.state === MessageState.Success) {
const toolCall = this.streamingToolCalls.value[0];
tabStorage.messages.push({
role: 'tool',
tool_call_id: toolCall.id || toolCall.function.name,
content: toolCallResult.content,
extraInfo: {
created: Date.now(),
state: toolCallResult.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
usage: this.completionUsage
}
});
} else if (toolCallResult.state === MessageState.ToolCall) {
const toolCall = this.streamingToolCalls.value[0];
tabStorage.messages.push({
role: 'tool',
tool_call_id: toolCall.id || toolCall.function.name,
content: toolCallResult.content,
extraInfo: {
created: Date.now(),
state: toolCallResult.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
usage: this.completionUsage
}
});
} }
} else if (this.streamingContent.value) { } else if (this.streamingContent.value) {

View File

@ -25,7 +25,7 @@
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'"> <div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
<Message.Toolcall <Message.Toolcall
:message="message" :tab-id="props.tabId" :message="message" :tab-id="props.tabId"
@update:tool-result="(value, index) => (message.toolResult || [])[index] = value" @update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
/> />
</div> </div>
</div> </div>
@ -97,6 +97,7 @@ const renderMessages = computed(() => {
messages.push({ messages.push({
role: 'assistant/tool_calls', role: 'assistant/tool_calls',
content: message.content, content: message.content,
toolResults: Array(message.tool_calls.length).fill([]),
tool_calls: message.tool_calls, tool_calls: message.tool_calls,
showJson: ref(false), showJson: ref(false),
extraInfo: { extraInfo: {
@ -116,8 +117,16 @@ const renderMessages = computed(() => {
// assistant // assistant
const lastAssistantMessage = messages[messages.length - 1]; const lastAssistantMessage = messages[messages.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') { if (lastAssistantMessage.role === 'assistant/tool_calls') {
lastAssistantMessage.toolResult = message.content; lastAssistantMessage.toolResults[message.index] = message.content;
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|| message.extraInfo.state !== MessageState.Success
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage; lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="message-role"> <div class="message-role">
<span class="message-reminder" v-if="!props.message.toolResult"> <span class="message-reminder" v-if="callingTools">
Agent 正在使用工具 Agent 正在使用工具
<span class="tool-loading iconfont icon-double-loading"> <span class="tool-loading iconfont icon-double-loading">
</span> </span>
@ -26,58 +26,77 @@
</div> </div>
</template> </template>
<div> <div v-for="(toolResult, toolIndex) in props.message.toolResults" :key="toolIndex"
<div class="tool-arguments"> class="toolcall-item">
<div class="inner">
<div v-html="jsonResultToHtml(props.message.tool_calls[0].function.arguments)"></div> <div class="tool-calls" v-if="toolIndex > 0">
<div class="tool-call-header">
<span class="tool-name">
<span class="iconfont icon-tool"></span>
{{ props.message.tool_calls[toolIndex].function.name }}
</span>
<el-button size="small" @click="createTest(props.message.tool_calls[toolIndex])">
<span class="iconfont icon-send"></span>
</el-button>
</div> </div>
</div> </div>
<div class="tool-arguments">
<el-scrollbar width="100%">
<div class="inner">
<div v-html="jsonResultToHtml(props.message.tool_calls[toolIndex].function.arguments)">
</div>
</div>
</el-scrollbar>
</div>
<!-- 工具调用结果 --> <!-- 工具调用结果 -->
<div v-if="props.message.toolResult"> <div v-if="toolResult.length > 0">
<div class="tool-call-header result"> <div class="tool-call-header result">
<span class="tool-name">
<span class="tool-name" v-if="isValid(toolResult)">
<span :class="`iconfont icon-info`"></span>
{{ t("response") }}
</span>
<span class="tool-name" v-else>
<span :class="`iconfont icon-${currentMessageLevel}`"></span> <span :class="`iconfont icon-${currentMessageLevel}`"></span>
{{ isValid ? t("response") : t('error') }} {{ isValid(toolResult) ? t("response") : t('error') }}
<el-button v-if="!isValid" size="small" <el-button size="small" @click="gotoIssue()">
@click="gotoIssue()"
>
{{ t('feedback') }} {{ t('feedback') }}
</el-button> </el-button>
</span> </span>
<span style="width: 200px;" class="tools-dialog-container" v-if="currentMessageLevel === 'info'">
<span style="width: 200px;" class="tools-dialog-container"
v-if="currentMessageLevel === 'info'">
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON" <el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
inactive-text="Text" style="margin-left: 10px; width: 200px;" inactive-text="Text" style="margin-left: 10px; width: 200px;"
:inactive-action-style="'backgroundColor: var(--sidebar)'" /> :inactive-action-style="'backgroundColor: var(--sidebar)'" />
</span> </span>
</div> </div>
<div class="tool-result" v-if="isValid"> <div class="tool-result" v-if="isValid(toolResult)">
<!-- 展示 JSON --> <!-- 展示 JSON -->
<div v-if="props.message.showJson!.value" class="tool-result-content"> <div v-if="props.message.showJson!.value" class="tool-result-content">
<div class="inner"> <div class="inner">
<div v-html="toHtml(props.message.toolResult)"></div> <div v-html="toHtml(props.message.toolResults[toolIndex])"></div>
</div> </div>
</div> </div>
<!-- 展示富文本 --> <!-- 展示富文本 -->
<span v-else> <span v-else>
<div v-for="(item, index) in props.message.toolResult" :key="index" <div v-for="(item, index) in props.message.toolResults[toolIndex]" :key="index"
class="response-item" class="response-item">
> <ToolcallResultItem :item="item"
<ToolcallResultItem @update:item="value => updateToolCallResultItem(value, toolIndex, index)"
:item="item" @update:ocr-done="value => collposePanel()" />
@update:item="value => updateToolCallResultItem(value, index)"
@update:ocr-done="value => collposePanel()"
/>
</div> </div>
</span> </span>
</div> </div>
<div v-else class="tool-result"> <div v-else class="tool-result">
<div class="tool-result-content" <div class="tool-result-content" v-for="(error, index) of collectErrors(toolResult)"
v-for="(error, index) of collectErrors" :key="index">
:key="index"
>
{{ error }} {{ error }}
</div> </div>
</div> </div>
@ -91,17 +110,12 @@
</div> </div>
<div class="tool-result-content"> <div class="tool-result-content">
<div class="progress"> <div class="progress">
<el-progress <el-progress :percentage="100" :format="() => ''" :indeterminate="true" text-inside />
:percentage="100"
:format="() => ''"
:indeterminate="true"
text-inside
/>
</div> </div>
</div> </div>
</div> </div>
<MessageMeta :message="message" /> <MessageMeta v-if="toolIndex === props.message.toolResults.length - 1" :message="message" />
</div> </div>
</el-collapse-item> </el-collapse-item>
@ -110,13 +124,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps, ref, watch, PropType, computed, defineEmits, inject, Ref } from 'vue'; import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import MessageMeta from './message-meta.vue'; import MessageMeta from './message-meta.vue';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown'; import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { createTest } from '@/views/setting/llm'; import { createTest } from '@/views/setting/llm';
import { IRenderMessage, MessageState } from '../chat-box/chat'; import { IToolRenderMessage, MessageState } from '../chat-box/chat';
import { ToolCallContent } from '@/hook/type'; import { ToolCallContent } from '@/hook/type';
import ToolcallResultItem from './toolcall-result-item.vue'; import ToolcallResultItem from './toolcall-result-item.vue';
@ -125,7 +139,7 @@ const { t } = useI18n();
const props = defineProps({ const props = defineProps({
message: { message: {
type: Object as PropType<IRenderMessage>, type: Object as PropType<IToolRenderMessage>,
required: true required: true
}, },
tabId: { tabId: {
@ -135,20 +149,38 @@ const props = defineProps({
}); });
const hasOcr = computed(() => { const hasOcr = computed(() => {
for (const item of props.message.toolResult || []) {
const metaInfo = item._meta || {}; if (props.message.role === 'assistant/tool_calls') {
const { ocr = false } = metaInfo; for (const toolResult of props.message.toolResults) {
if (ocr) { for (const item of toolResult) {
return true; const metaInfo = item._meta || {};
const { ocr = false } = metaInfo;
if (ocr) {
return true;
}
}
} }
} }
return false; return false;
}); });
const activeNames = ref<string[]>(props.message.toolResult ? [''] : ['tool']);
const callingTools = computed(() => {
const emptyToolResult = props.message.toolResults.find(item => item.length === 0);
if (emptyToolResult) {
return true;
}
return false;
});
const activeNames = ref<string[]>(callingTools.value ? ['tool']: []);
watch( watch(
() => props.message.toolResult, () => props.message,
(value, _) => { (value, _) => {
if (hasOcr.value) { if (hasOcr.value) {
return; return;
@ -180,10 +212,10 @@ const jsonResultToHtml = (jsonResult: string) => {
try { try {
const formattedJson = JSON.stringify(JSON.parse(jsonResult), null, 2); const formattedJson = JSON.stringify(JSON.parse(jsonResult), null, 2);
const html = markdownToHtml('```json\n' + formattedJson + '\n```'); const html = markdownToHtml('```json\n' + formattedJson + '\n```');
return html; return html;
} catch (error) { } catch (error) {
const html = markdownToHtml('```json\n' + jsonResult + '\n```'); const html = markdownToHtml('```json\n' + jsonResult + '\n```');
return html; return html;
} }
} }
@ -191,9 +223,10 @@ function gotoIssue() {
window.open('https://github.com/LSTM-Kirigaya/openmcp-client/issues', '_blank'); window.open('https://github.com/LSTM-Kirigaya/openmcp-client/issues', '_blank');
} }
const isValid = computed(() => {
function isValid(toolResult: ToolCallContent[]) {
try { try {
const item = props.message.toolResult![0]; const item = toolResult[0];
if (item.type === 'error') { if (item.type === 'error') {
return false; return false;
} }
@ -201,31 +234,36 @@ const isValid = computed(() => {
} catch { } catch {
return false; return false;
} }
}); }
const currentMessageLevel = computed(() => { const currentMessageLevel = computed(() => {
// mcp server // mcp server
if (!props.message.toolResult) { for (const toolResult of props.message.toolResults) {
return 'info'; if (toolResult.length === 0) {
return 'info';
}
if (!isValid(toolResult)) {
return 'error';
}
} }
if (!isValid.value) { if (props.message.extraInfo.state !== MessageState.Success) {
return 'error';
}
if (props.message.extraInfo.state != MessageState.Success) {
return 'warning'; return 'warning';
} }
return 'info';
})
const collectErrors = computed(() => { return 'info';
});
function collectErrors(toolResult: ToolCallContent[]) {
const errorMessages = []; const errorMessages = [];
try { try {
const errorResults = props.message.toolResult!.filter(item => item.type === 'error'); const errorResults = toolResult.filter(item => item.type === 'error');
console.log(errorResults); console.log(errorResults);
for (const errorResult of errorResults) { for (const errorResult of errorResults) {
errorMessages.push(errorResult.text); errorMessages.push(errorResult.text);
} }
@ -233,12 +271,12 @@ const collectErrors = computed(() => {
} catch { } catch {
return errorMessages; return errorMessages;
} }
}); }
const emit = defineEmits(['update:tool-result']); const emits = defineEmits(['update:tool-result']);
function updateToolCallResultItem(value: any, index: number) { function updateToolCallResultItem(value: any, toolIndex: number, index: number) {
emit('update:tool-result', value, index); emits('update:tool-result', value, toolIndex, index);
} }
</script> </script>
@ -250,9 +288,6 @@ function updateToolCallResultItem(value: any, index: number) {
padding: 3px 10px; padding: 3px 10px;
} }
.tool-result-content .el-progress-bar__outer {
}
.tool-result-content .progress { .tool-result-content .progress {
border-radius: .5em; border-radius: .5em;
background-color: var(--el-fill-color-light) !important; background-color: var(--el-fill-color-light) !important;
@ -269,7 +304,7 @@ function updateToolCallResultItem(value: any, index: number) {
} }
.message-text.tool_calls.warning .tool-result { .message-text.tool_calls.warning .tool-result {
background-color: rgba(230, 162, 60, 0.5); background-color: rgba(230, 162, 60, 0.5);
} }
.message-text.tool_calls.error { .message-text.tool_calls.error {
@ -281,7 +316,7 @@ function updateToolCallResultItem(value: any, index: number) {
} }
.message-text.tool_calls.error .tool-result { .message-text.tool_calls.error .tool-result {
background-color: rgba(245, 108, 108, 0.5); background-color: rgba(245, 108, 108, 0.5);
} }
@ -295,6 +330,9 @@ function updateToolCallResultItem(value: any, index: number) {
padding-bottom: 5px; padding-bottom: 5px;
} }
.toolcall-item .tool-calls {
margin-top: 22px;
}
.tool-call-item { .tool-call-item {
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -29,9 +29,6 @@ export function callTool(toolName: string, toolArgs: Record<string, any>) {
resolve(data.msg); resolve(data.msg);
} }
}, { once: true }); }, { once: true });
pinkLog('callTool');
console.log(toolArgs);
bridge.postMessage({ bridge.postMessage({
command: 'tools/call', command: 'tools/call',