修复工具调用的错误
This commit is contained in:
parent
ad857e6544
commit
f925da7d7d
@ -15,7 +15,7 @@ import MainPanel from '@/components/main-panel/index.vue';
|
||||
import { setDefaultCss } from './hook/css';
|
||||
import { greenLog, pinkLog } from './views/setting/util';
|
||||
import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge';
|
||||
import { connectionArgs, connectionMethods, doConnect, launchConnect } from './views/connect/connection';
|
||||
import { connectionArgs, connectionMethods, doConnect, launchConnect, loadEnvVar } from './views/connect/connection';
|
||||
import { loadSetting } from './hook/setting';
|
||||
import { loadPanels } from './hook/panel';
|
||||
|
||||
@ -29,13 +29,16 @@ bridge.addCommandListener('hello', data => {
|
||||
|
||||
|
||||
function initDebug() {
|
||||
connectionArgs.commandString = 'node C:/Users/K/code/servers/src/puppeteer/dist/index.js';
|
||||
connectionArgs.commandString = 'node /Users/bytedance/projects/mcp/servers/src/puppeteer/dist/index.js';
|
||||
connectionMethods.current = 'STDIO';
|
||||
|
||||
setTimeout(async () => {
|
||||
// 初始化 设置
|
||||
loadSetting();
|
||||
|
||||
// 初始化环境变量
|
||||
loadEnvVar();
|
||||
|
||||
// 尝试连接
|
||||
await doConnect();
|
||||
|
||||
@ -56,6 +59,9 @@ async function initProduce() {
|
||||
// 初始化 设置
|
||||
loadSetting();
|
||||
|
||||
// 初始化环境变量
|
||||
loadEnvVar();
|
||||
|
||||
// 尝试连接
|
||||
await launchConnect();
|
||||
|
||||
|
@ -93,18 +93,6 @@ export default defineComponent({
|
||||
return JSON.stringify(obj1) === JSON.stringify(obj2)
|
||||
}
|
||||
|
||||
// 自动调整文本区域高度
|
||||
const adjustTextareaHeight = () => {
|
||||
nextTick(() => {
|
||||
if (textareaRef.value) {
|
||||
textareaRef.value.style.height = 'auto'
|
||||
textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(inputValue, adjustTextareaHeight, { immediate: true })
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === '{') {
|
||||
event.preventDefault();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ToolItem } from "@/hook/type";
|
||||
import { ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import { Ref, ref } from "vue";
|
||||
|
||||
import type { OpenAI } from 'openai';
|
||||
@ -24,8 +24,17 @@ export interface IExtraInfo {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant' | 'system' | 'tool';
|
||||
export interface ToolMessage {
|
||||
role: 'tool';
|
||||
content: ToolCallContent[];
|
||||
tool_call_id?: string
|
||||
name?: string // 工具名称,当 role 为 tool
|
||||
tool_calls?: ToolCall[],
|
||||
extraInfo: IExtraInfo
|
||||
}
|
||||
|
||||
export interface TextMessage {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
tool_call_id?: string
|
||||
name?: string // 工具名称,当 role 为 tool
|
||||
@ -33,6 +42,8 @@ export interface ChatMessage {
|
||||
extraInfo: IExtraInfo
|
||||
}
|
||||
|
||||
export type ChatMessage = ToolMessage | TextMessage;
|
||||
|
||||
// 新增状态和工具数据
|
||||
interface EnableToolItem {
|
||||
name: string;
|
||||
@ -69,7 +80,7 @@ export const allTools = ref<ToolItem[]>([]);
|
||||
export interface IRenderMessage {
|
||||
role: 'user' | 'assistant/content' | 'assistant/tool_calls' | 'tool';
|
||||
content: string;
|
||||
toolResult?: string;
|
||||
toolResult?: ToolCallContent[];
|
||||
tool_calls?: ToolCall[];
|
||||
showJson?: Ref<boolean>;
|
||||
extraInfo: IExtraInfo;
|
||||
|
@ -5,9 +5,12 @@
|
||||
<div v-for="(message, index) in renderMessages" :key="index"
|
||||
:class="['message-item', message.role.split('/')[0]]"
|
||||
>
|
||||
<div class="message-avatar" v-if="message.role.startsWith('assistant')">
|
||||
<div class="message-avatar" v-if="message.role === 'assistant/content'">
|
||||
<span class="iconfont icon-robot"></span>
|
||||
</div>
|
||||
<div class="message-avatar" v-else-if="message.role === 'assistant/tool_calls'">
|
||||
<span class="iconfont icon-tool"></span>
|
||||
</div>
|
||||
|
||||
<!-- 用户输入的部分 -->
|
||||
<div class="message-content" v-if="message.role === 'user'">
|
||||
@ -27,22 +30,7 @@
|
||||
|
||||
<!-- 正在加载的部分实时解析 markdown -->
|
||||
<div v-if="isLoading" class="message-item assistant">
|
||||
<div class="message-avatar">
|
||||
<span class="iconfont icon-chat"></span>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-role">
|
||||
Agent
|
||||
<span class="message-reminder">
|
||||
正在生成答案
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
<span v-html="waitingMarkdownToHtml(streamingContent)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<Message.StreamingBox :streaming-content="streamingContent" />
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@ -84,26 +72,16 @@ import { ElMessage, ScrollbarInstance } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { ChatMessage, ChatStorage, IRenderMessage, MessageState, ToolCall } from './chat';
|
||||
|
||||
import Setting from './setting.vue';
|
||||
|
||||
// 引入 markdown.ts 中的函数
|
||||
import { markdownToHtml } from './markdown';
|
||||
import { TaskLoop } from './task-loop';
|
||||
import { llmManager, llms } from '@/views/setting/llm';
|
||||
|
||||
import * as Message from './message';
|
||||
import Setting from './setting.vue';
|
||||
|
||||
defineComponent({ name: 'chat' });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function waitingMarkdownToHtml(content: string) {
|
||||
if (content) {
|
||||
return markdownToHtml(content);
|
||||
}
|
||||
return '<span class="typing-cursor">|</span>';
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
@ -358,6 +336,7 @@ onUnmounted(() => {
|
||||
|
||||
.message-avatar {
|
||||
margin-right: 12px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Assistant from "./assistant.vue";
|
||||
import Toolcall from "./toolcall.vue";
|
||||
import User from "./user.vue";
|
||||
export { Assistant, Toolcall, User };
|
||||
import StreamingBox from "./streaming-box.vue";
|
||||
export { Assistant, Toolcall, User, StreamingBox };
|
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="message-avatar">
|
||||
<span class="iconfont icon-chat"></span>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-role">
|
||||
Agent
|
||||
<span class="message-reminder">
|
||||
正在生成答案
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
<span v-html="waitingMarkdownToHtml(streamingContent)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
import { markdownToHtml } from '../markdown';
|
||||
|
||||
const props = defineProps({
|
||||
streamingContent: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
function waitingMarkdownToHtml(content: string) {
|
||||
if (content) {
|
||||
return markdownToHtml(content);
|
||||
}
|
||||
return '<span class="typing-cursor">|</span>';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="message-role">
|
||||
Agent
|
||||
<span class="message-reminder" v-if="!props.message.toolResult">
|
||||
正在使用工具
|
||||
Agent 正在使用工具
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
@ -16,7 +15,6 @@
|
||||
<template #title>
|
||||
<div class="tool-calls">
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-type">{{ 'tool use' }}</span>
|
||||
<span class="tool-name">{{ props.message.tool_calls[0].function.name }}</span>
|
||||
<el-button size="small" @click="createTest(props.message.tool_calls[0])">
|
||||
<span class="iconfont icon-send"></span>
|
||||
@ -34,24 +32,30 @@
|
||||
|
||||
<!-- 工具调用结果 -->
|
||||
<div v-if="props.message.toolResult">
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-name" :class="{ 'error': !isValidJson }">
|
||||
{{ isValidJson ? '响应': '错误' }}
|
||||
<div class="tool-call-header result">
|
||||
<span class="tool-name" :class="{ 'error': !isValid }">
|
||||
{{ isValid ? '响应': '错误' }}
|
||||
|
||||
<el-button v-if="!isValid" size="small"
|
||||
@click="gotoIssue()"
|
||||
>
|
||||
反馈
|
||||
</el-button>
|
||||
</span>
|
||||
<span style="width: 200px;" class="tools-dialog-container" v-if="isValidJson">
|
||||
<span style="width: 200px;" class="tools-dialog-container" v-if="isValid">
|
||||
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
|
||||
inactive-text="Text" style="margin-left: 10px; width: 200px;"
|
||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="tool-result" v-if="isValidJson">
|
||||
<div class="tool-result" v-if="isValid">
|
||||
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
||||
<div class="inner">
|
||||
<div v-html="jsonResultToHtml(props.message.toolResult)"></div>
|
||||
<div v-html="toHtml(props.message.toolResult)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>
|
||||
<div v-for="(item, index) in JSON.parse(props.message.toolResult)" :key="index"
|
||||
<div v-for="(item, index) in props.message.toolResult" :key="index"
|
||||
class="response-item"
|
||||
>
|
||||
<el-scrollbar width="100%">
|
||||
@ -68,19 +72,23 @@
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="tool-result" :class="{ 'error': !isValidJson }">
|
||||
<div class="tool-result-content">
|
||||
<div class="inner">
|
||||
{{ props.message.toolResult }}
|
||||
</div>
|
||||
<div v-else class="tool-result" :class="{ 'error': !isValid }">
|
||||
<div class="tool-result-content"
|
||||
v-for="(error, index) of collectErrors"
|
||||
:key="index"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<MessageMeta :message="message" />
|
||||
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<MessageMeta :message="message" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -90,6 +98,7 @@ import MessageMeta from './message-meta.vue';
|
||||
import { markdownToHtml } from '../markdown';
|
||||
import { createTest } from '@/views/setting/llm';
|
||||
import { IRenderMessage, MessageState } from '../chat';
|
||||
import { ToolCallContent } from '@/hook/type';
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
@ -112,22 +121,54 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
const jsonResultToHtml = (jsonString: string) => {
|
||||
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
||||
const toHtml = (toolResult: ToolCallContent[]) => {
|
||||
const formattedJson = JSON.stringify(toolResult, null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
const isValidJson = computed(() => {
|
||||
const jsonResultToHtml = (jsonResult: string) => {
|
||||
try {
|
||||
JSON.parse(props.message.toolResult || '');
|
||||
const formattedJson = JSON.stringify(JSON.parse(jsonResult), null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
return html;
|
||||
} catch (error) {
|
||||
const html = markdownToHtml('```json\n' + jsonResult + '\n```');
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
function gotoIssue() {
|
||||
window.open('https://github.com/LSTM-Kirigaya/openmcp-client/issues', '_blank');
|
||||
}
|
||||
|
||||
const isValid = computed(() => {
|
||||
try {
|
||||
const item = props.message.toolResult![0];
|
||||
if (item.type === 'error') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const collectErrors = computed(() => {
|
||||
const errorMessages = [];
|
||||
try {
|
||||
const errorResults = props.message.toolResult!.filter(item => item.type === 'error');
|
||||
console.log(errorResults);
|
||||
|
||||
for (const errorResult of errorResults) {
|
||||
errorMessages.push(errorResult.text);
|
||||
}
|
||||
return errorMessages;
|
||||
} catch {
|
||||
return errorMessages;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -140,10 +181,17 @@ const isValidJson = computed(() => {
|
||||
border-left: 3px solid var(--el-color-error);
|
||||
}
|
||||
|
||||
.tool-calls {
|
||||
margin-top: 10px;
|
||||
.message-text .el-collapse-item__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.message-text .el-collapse-item__content {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
.tool-call-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@ -151,6 +199,9 @@ const isValidJson = computed(() => {
|
||||
.tool-call-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tool-call-header.result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
<el-tooltip :content="t('tool-use')" placement="top">
|
||||
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small"
|
||||
@click="toggleTools">
|
||||
<span class="iconfont icon-tool badge-outer" v-if="availableToolsNum > 0">
|
||||
<span class="iconfont icon-tool badge-outer">
|
||||
<span class="badge-inner">
|
||||
{{ availableToolsNum }}
|
||||
</span>
|
||||
|
@ -46,20 +46,15 @@ export class TaskLoop {
|
||||
const toolResponse = await callTool(toolName, toolArgs);
|
||||
|
||||
if (!toolResponse.isError) {
|
||||
const content = JSON.stringify(toolResponse.content);
|
||||
// const content = JSON.stringify(toolResponse.content);
|
||||
return {
|
||||
content,
|
||||
state: MessageState.Success,
|
||||
|
||||
content: toolResponse.content,
|
||||
state: MessageState.Success
|
||||
};
|
||||
} else {
|
||||
this.onError({
|
||||
state: MessageState.ToolCall,
|
||||
msg: `工具调用失败: ${toolResponse.content}`
|
||||
});
|
||||
console.error(toolResponse.content);
|
||||
|
||||
return {
|
||||
content: toolResponse.content.toString(),
|
||||
content: toolResponse.content,
|
||||
state: MessageState.ToolCall
|
||||
}
|
||||
}
|
||||
@ -70,13 +65,27 @@ export class TaskLoop {
|
||||
msg: `工具调用失败: ${(error as Error).message}`
|
||||
});
|
||||
console.error(error);
|
||||
|
||||
return {
|
||||
content: (error as Error).message,
|
||||
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) {
|
||||
const content = chunk.choices[0]?.delta?.content || '';
|
||||
if (content) {
|
||||
@ -271,7 +280,11 @@ export class TaskLoop {
|
||||
});
|
||||
|
||||
const toolCallResult = await this.handleToolCalls(this.streamingToolCalls.value);
|
||||
if (toolCallResult) {
|
||||
|
||||
console.log(toolCallResult);
|
||||
|
||||
|
||||
if (toolCallResult.content) {
|
||||
const toolCall = this.streamingToolCalls.value[0];
|
||||
|
||||
tabStorage.messages.push({
|
||||
|
@ -12,7 +12,7 @@ export const promptsManager = reactive<{
|
||||
export interface PromptStorage {
|
||||
currentPromptName: string;
|
||||
lastPromptGetResponse?: PromptsGetResponse;
|
||||
formData: Record<string, number | boolean | string>;
|
||||
formData: Record<string, any>;
|
||||
}
|
||||
|
||||
export function parsePromptTemplate(template: string): {
|
||||
|
@ -13,7 +13,7 @@ export const resourcesManager = reactive<{
|
||||
export interface ResourceStorage {
|
||||
currentResourceName: string;
|
||||
lastResourceReadResponse?: ResourcesReadResponse;
|
||||
formData: Record<string, number | string | boolean>;
|
||||
formData: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,6 @@
|
||||
v-model="tabStorage.formData[name]"
|
||||
/>
|
||||
|
||||
|
||||
<k-input-object
|
||||
v-else-if="property.type === 'object'"
|
||||
v-model="tabStorage.formData[name]"
|
||||
|
@ -3,35 +3,34 @@
|
||||
<span>
|
||||
<span>{{ t('response') }}</span>
|
||||
<span style="width: 200px;">
|
||||
<el-switch
|
||||
v-model="showRawJson"
|
||||
inline-prompt
|
||||
active-text="JSON"
|
||||
inactive-text="Text"
|
||||
<el-switch v-model="showRawJson" inline-prompt active-text="JSON" inactive-text="Text"
|
||||
style="margin-left: 10px; width: 200px;"
|
||||
:inactive-action-style="'backgroundColor: var(--sidebar)'"
|
||||
/>
|
||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||
</span>
|
||||
</span>
|
||||
<el-scrollbar height="500px">
|
||||
<div
|
||||
class="output-content"
|
||||
contenteditable="false"
|
||||
>
|
||||
<template v-if="!showRawJson">
|
||||
<template v-if="tabStorage.lastToolCallResponse?.isError">
|
||||
<span style="color: var(--el-color-error)">
|
||||
{{ tabStorage.lastToolCallResponse.content.map(c => c.text).join('\n') }}
|
||||
<div class="output-content" contenteditable="false">
|
||||
|
||||
<!-- TODO: 更加稳定,现在通过下面这个来判断上一次执行结果是否成功 -->
|
||||
<div v-if="typeof tabStorage.lastToolCallResponse === 'string'" class="error-tool-call">
|
||||
<span>
|
||||
{{ tabStorage.lastToolCallResponse }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- 展示原本的信息 -->
|
||||
<template v-if="!showRawJson">
|
||||
{{tabStorage.lastToolCallResponse?.content.map(c => c.text).join('\n')}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ tabStorage.lastToolCallResponse?.content.map(c => c.text).join('\n') }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 展示 json -->
|
||||
<template v-else>
|
||||
{{ formattedJson }}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
@ -60,17 +59,15 @@ const showRawJson = ref(false);
|
||||
|
||||
const formattedJson = computed(() => {
|
||||
try {
|
||||
if (typeof tabStorage.lastToolCallResponse === 'string') {
|
||||
return tabStorage.lastToolCallResponse;
|
||||
}
|
||||
return JSON.stringify(tabStorage.lastToolCallResponse, null, 2);
|
||||
} catch {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
});
|
||||
|
||||
const jsonResultToHtml = (jsonString: string) => {
|
||||
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
return html;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -93,7 +90,7 @@ const jsonResultToHtml = (jsonString: string) => {
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
.tool-logger > span:first-child {
|
||||
.tool-logger>span:first-child {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -113,4 +110,10 @@ const jsonResultToHtml = (jsonString: string) => {
|
||||
line-height: 1.5;
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
.error-tool-call {
|
||||
background-color: rgba(245, 108, 108, 0.5);
|
||||
padding: 5px 9px;
|
||||
border-radius: .5em;
|
||||
}
|
||||
</style>
|
@ -11,7 +11,7 @@ export const toolsManager = reactive<{
|
||||
|
||||
export interface ToolStorage {
|
||||
currentToolName: string;
|
||||
lastToolCallResponse?: ToolCallResponse;
|
||||
lastToolCallResponse?: ToolCallResponse | string;
|
||||
formData: Record<string, any>;
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export function callTool(toolName: string, toolArgs: Record<string, any>) {
|
||||
console.log(data.msg);
|
||||
|
||||
if (data.code !== 200) {
|
||||
reject(new Error(data.msg + ''));
|
||||
resolve(data.msg);
|
||||
} else {
|
||||
resolve(data.msg);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ interface TypeAble {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export function getDefaultValue(property: TypeAble) {
|
||||
export function getDefaultValue(property: TypeAble): any {
|
||||
if (property.type === 'number' || property.type === 'integer') {
|
||||
return 0;
|
||||
} else if (property.type === 'boolean') {
|
||||
|
@ -81,6 +81,7 @@ export interface PromptsGetResponse {
|
||||
export interface ToolCallContent {
|
||||
type: string;
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ToolCallResponse {
|
||||
|
@ -7,7 +7,7 @@
|
||||
</span>
|
||||
|
||||
<p>
|
||||
OpenMCP Client 0.0.4 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
||||
OpenMCP Client 0.0.5 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -389,3 +389,74 @@ export function getServerVersion() {
|
||||
export const envVarStatus = {
|
||||
launched: false
|
||||
};
|
||||
|
||||
function lookupEnvVar(varNames: string[]) {
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
return new Promise<string[] | undefined>((resolve, reject) => {
|
||||
bridge.addCommandListener('lookup-env-var', data => {
|
||||
const { code, msg } = data;
|
||||
|
||||
if (code === 200) {
|
||||
connectionResult.logString.push({
|
||||
type: 'info',
|
||||
message: '预设环境变量同步完成'
|
||||
});
|
||||
|
||||
resolve(msg);
|
||||
} else {
|
||||
connectionResult.logString.push({
|
||||
type: 'error',
|
||||
message: '预设环境变量同步失败: ' + msg
|
||||
});
|
||||
|
||||
resolve(undefined);
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
console.log(varNames);
|
||||
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'lookup-env-var',
|
||||
data: {
|
||||
keys: varNames
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function handleEnvSwitch(enabled: boolean) {
|
||||
const presetVars = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||
|
||||
if (enabled) {
|
||||
const values = await lookupEnvVar(presetVars);
|
||||
|
||||
if (values) {
|
||||
// 将 key values 合并进 connectionEnv.data 中
|
||||
// 若已有相同的 key, 则替换 value
|
||||
for (let i = 0; i < presetVars.length; i++) {
|
||||
const key = presetVars[i];
|
||||
const value = values[i];
|
||||
const sameNameItems = connectionEnv.data.filter(item => item.key === key);
|
||||
if (sameNameItems.length > 0) {
|
||||
const conflictItem = sameNameItems[0];
|
||||
conflictItem.value = value;
|
||||
} else {
|
||||
connectionEnv.data.push({
|
||||
key: key, value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
||||
const reserveItems = connectionEnv.data.filter(item => !presetVars.includes(item.key));
|
||||
connectionEnv.data = reserveItems;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadEnvVar() {
|
||||
return await handleEnvSwitch(true);
|
||||
}
|
@ -42,51 +42,13 @@
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionEnv, connectionResult, EnvItem, envVarStatus } from './connection';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { connectionEnv, EnvItem, handleEnvSwitch } from './connection';
|
||||
|
||||
defineComponent({ name: 'env-var' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
function lookupEnvVar(varNames: string[]) {
|
||||
console.log('enter');
|
||||
|
||||
return new Promise<string[] | undefined>((resolve, reject) => {
|
||||
bridge.addCommandListener('lookup-env-var', data => {
|
||||
const { code, msg } = data;
|
||||
|
||||
if (code === 200) {
|
||||
connectionResult.logString.push({
|
||||
type: 'info',
|
||||
message: '预设环境变量同步完成'
|
||||
});
|
||||
|
||||
resolve(msg);
|
||||
} else {
|
||||
connectionResult.logString.push({
|
||||
type: 'error',
|
||||
message: '预设环境变量同步失败: ' + msg
|
||||
});
|
||||
|
||||
resolve(undefined);
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
console.log(varNames);
|
||||
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'lookup-env-var',
|
||||
data: {
|
||||
keys: varNames
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加环境变量
|
||||
@ -126,46 +88,6 @@ function deleteEnvVar(option: EnvItem) {
|
||||
|
||||
const envEnabled = ref(true);
|
||||
|
||||
async function handleEnvSwitch(enabled: boolean) {
|
||||
const presetVars = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||
|
||||
if (enabled) {
|
||||
const values = await lookupEnvVar(presetVars);
|
||||
|
||||
if (values) {
|
||||
// 将 key values 合并进 connectionEnv.data 中
|
||||
// 若已有相同的 key, 则替换 value
|
||||
for (let i = 0; i < presetVars.length; i++) {
|
||||
const key = presetVars[i];
|
||||
const value = values[i];
|
||||
const sameNameItems = connectionEnv.data.filter(item => item.key === key);
|
||||
if (sameNameItems.length > 0) {
|
||||
const conflictItem = sameNameItems[0];
|
||||
conflictItem.value = value;
|
||||
} else {
|
||||
connectionEnv.data.push({
|
||||
key: key, value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
||||
const reserveItems = connectionEnv.data.filter(item => !presetVars.includes(item.key));
|
||||
connectionEnv.data = reserveItems;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (envVarStatus.launched) {
|
||||
return;
|
||||
}
|
||||
handleEnvSwitch(envEnabled.value);
|
||||
envVarStatus.launched = true;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
1
service/.gitignore
vendored
1
service/.gitignore
vendored
@ -25,3 +25,4 @@ config.json
|
||||
setting.json
|
||||
|
||||
tabs.example-servers/puppeteer.json
|
||||
*.traineddata
|
73
service/package-lock.json
generated
73
service/package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"pako": "^2.1.0",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"tesseract.js": "^6.0.1",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -768,6 +769,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@ -1598,6 +1605,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/idb-keyval": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
||||
"integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@ -1692,6 +1705,12 @@
|
||||
"resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
||||
},
|
||||
"node_modules/is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
|
||||
@ -2028,6 +2047,15 @@
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/opencollective-postinstall": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"opencollective-postinstall": "index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
@ -2262,6 +2290,12 @@
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@ -2604,6 +2638,30 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tesseract.js": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-6.0.1.tgz",
|
||||
"integrity": "sha512-/sPvMvrCtgxnNRCjbTYbr7BRu0yfWDsMZQ2a/T5aN/L1t8wUQN6tTWv6p6FwzpoEBA0jrN2UD2SX4QQFRdoDbA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bmp-js": "^0.1.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"is-url": "^1.2.4",
|
||||
"node-fetch": "^2.6.9",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"tesseract.js-core": "^6.0.0",
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"zlibjs": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tesseract.js-core": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-6.0.0.tgz",
|
||||
"integrity": "sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
@ -2820,6 +2878,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
|
||||
"integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "4.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
||||
@ -2905,6 +2969,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zlibjs": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
|
||||
"integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.24.2",
|
||||
"resolved": "https://registry.npmmirror.com/zod/-/zod-3.24.2.tgz",
|
||||
|
@ -41,6 +41,7 @@
|
||||
"pako": "^2.1.0",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"tesseract.js": "^6.0.1",
|
||||
"ws": "^8.18.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PostMessageble } from "../adapter";
|
||||
import { MCPClient } from "./connect";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { MCPClient } from "../hook/client";
|
||||
|
||||
|
||||
export async function lookupEnvVarHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
import { PostMessageble } from '../adapter';
|
||||
import { connect, MCPClient, type MCPOptions } from './connect';
|
||||
import { PostMessageble } from '../hook/adapter';
|
||||
import { connect, MCPClient, type MCPOptions } from '../hook/client';
|
||||
import { lookupEnvVarHandler } from './env-var';
|
||||
import { callTool, getPrompt, getServerVersion, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './handler';
|
||||
import { callTool, getPrompt, getServerVersion, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './mcp-server';
|
||||
import { chatCompletionHandler } from './llm';
|
||||
import { panelLoadHandler, panelSaveHandler } from './panel';
|
||||
import { settingLoadHandler, settingSaveHandler } from './setting';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { OpenAI } from 'openai';
|
||||
import { MCPClient } from './connect';
|
||||
import { PostMessageble } from '../adapter';
|
||||
import { MCPClient } from '../hook/client';
|
||||
import { PostMessageble } from '../hook/adapter';
|
||||
|
||||
let currentStream: AsyncIterable<any> | null = null;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PostMessageble } from "../adapter";
|
||||
import { MCPClient } from "./connect";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { MCPClient } from "../hook/client";
|
||||
|
||||
// ==================== 接口定义 ====================
|
||||
export interface GetPromptOption {
|
0
service/src/controller/ocr.ts
Normal file
0
service/src/controller/ocr.ts
Normal file
@ -1,6 +1,6 @@
|
||||
import { PostMessageble } from '../adapter';
|
||||
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../util';
|
||||
import { MCPClient } from './connect';
|
||||
import { PostMessageble } from '../hook/adapter';
|
||||
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../hook/setting';
|
||||
import { MCPClient } from '../hook/client';
|
||||
|
||||
export async function panelSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
||||
try {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PostMessageble } from '../adapter';
|
||||
import { loadConfig, saveConfig } from '../util';
|
||||
import { MCPClient } from './connect';
|
||||
import { PostMessageble } from '../hook/adapter';
|
||||
import { loadConfig, saveConfig } from '../hook/setting';
|
||||
import { MCPClient } from '../hook/client';
|
||||
|
||||
export async function settingSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
||||
try {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PostMessageble } from "../adapter";
|
||||
import { MCPClient } from "./connect";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { MCPClient } from "../hook/client";
|
||||
|
||||
export function ping(client: MCPClient | undefined, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
|
22
service/src/hook/ocr.ts
Normal file
22
service/src/hook/ocr.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import Tesseract from 'tesseract.js';
|
||||
|
||||
async function tesseractOCR(
|
||||
imagePath: string,
|
||||
logger: (message: Tesseract.LoggerMessage) => void,
|
||||
lang: string = 'eng+chi_sim'
|
||||
) {
|
||||
try {
|
||||
const { data: { text } } = await Tesseract.recognize(
|
||||
imagePath,
|
||||
lang,
|
||||
{
|
||||
logger
|
||||
}
|
||||
);
|
||||
|
||||
return text;
|
||||
} catch (error) {
|
||||
console.error('OCR error:', error);
|
||||
}
|
||||
return '无法识别图片';
|
||||
}
|
@ -2,7 +2,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { llms } from './llm';
|
||||
import { IServerVersion } from './controller/connect';
|
||||
import { IServerVersion } from './client';
|
||||
|
||||
export let VSCODE_WORKSPACE = '';
|
||||
|
@ -1,3 +1,12 @@
|
||||
// server/src/types.ts
|
||||
export interface IMessage {
|
||||
type: string;
|
||||
data: Record<string, unknown>;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: IMessage) => void;
|
||||
|
||||
// ==================== 基础类型定义 ====================
|
||||
export interface SchemaProperty {
|
||||
title: string;
|
@ -1,5 +1,5 @@
|
||||
export { messageController } from './controller';
|
||||
export { VSCodeWebViewLike } from './adapter';
|
||||
export { setVscodeWorkspace } from './util';
|
||||
export { VSCodeWebViewLike } from './hook/adapter';
|
||||
export { setVscodeWorkspace } from './hook/setting';
|
||||
// TODO: 更加规范
|
||||
export { client } from './controller';
|
@ -1,8 +0,0 @@
|
||||
// server/src/types.ts
|
||||
export interface IMessage {
|
||||
type: string;
|
||||
data: Record<string, unknown>;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: IMessage) => void;
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"currentIndex": 0,
|
||||
"currentIndex": 1,
|
||||
"tabs": [
|
||||
{
|
||||
"name": "交互测试",
|
||||
@ -171,7 +171,7 @@
|
||||
"storage": {
|
||||
"currentToolName": "get_weather_by_city_code",
|
||||
"formData": {
|
||||
"city_code": 101210101
|
||||
"city_code": 0
|
||||
},
|
||||
"lastToolCallResponse": {
|
||||
"content": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user