修复一轮bug
This commit is contained in:
parent
0f947c5b09
commit
da482e26a9
@ -1,5 +1,14 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
|
||||||
|
## [main] 0.0.8
|
||||||
|
- 大模型 API 测试时更加完整的报错
|
||||||
|
- 修复 0.0.7 引入的bug:修改对话无法发出
|
||||||
|
- 修复 bug:富文本编辑器粘贴文本会带样式
|
||||||
|
- 修复 bug:富文本编辑器发送前缀为空的字符会全部为空
|
||||||
|
- 修复 bug:流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败
|
||||||
|
- 修复 bug:大模型返回大量重复错误信息
|
||||||
|
|
||||||
## [main] 0.0.7
|
## [main] 0.0.7
|
||||||
- 优化页面布局,使得调试窗口可以显示更多内容
|
- 优化页面布局,使得调试窗口可以显示更多内容
|
||||||
- 扩大默认的上下文长度 10 -> 20
|
- 扩大默认的上下文长度 10 -> 20
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"displayName": "OpenMCP",
|
"displayName": "OpenMCP",
|
||||||
"description": "An all in one MCP Client/TestTool",
|
"description": "An all in one MCP Client/TestTool",
|
||||||
"version": "0.0.7",
|
"version": "0.0.8",
|
||||||
"publisher": "kirigaya",
|
"publisher": "kirigaya",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "kirigaya",
|
"name": "kirigaya",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4870215 */
|
font-family: "iconfont"; /* Project id 4870215 */
|
||||||
src: url('iconfont.woff2?t=1746529081655') format('woff2'),
|
src: url('iconfont.woff2?t=1746703816245') format('woff2'),
|
||||||
url('iconfont.woff?t=1746529081655') format('woff'),
|
url('iconfont.woff?t=1746703816245') format('woff'),
|
||||||
url('iconfont.ttf?t=1746529081655') format('truetype');
|
url('iconfont.ttf?t=1746703816245') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,10 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-waiting:before {
|
||||||
|
content: "\e6d0";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-timeout:before {
|
.icon-timeout:before {
|
||||||
content: "\edf5";
|
content: "\edf5";
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -61,6 +61,9 @@ const streamingContent = inject('streamingContent') as Ref<string>;
|
|||||||
const streamingToolCalls = inject('streamingToolCalls') as Ref<ToolCall[]>;
|
const streamingToolCalls = inject('streamingToolCalls') as Ref<ToolCall[]>;
|
||||||
const scrollToBottom = inject('scrollToBottom') as () => Promise<void>;
|
const scrollToBottom = inject('scrollToBottom') as () => Promise<void>;
|
||||||
const updateScrollHeight = inject('updateScrollHeight') as () => void;
|
const updateScrollHeight = inject('updateScrollHeight') as () => void;
|
||||||
|
const chatContext = inject('chatContext') as any;
|
||||||
|
|
||||||
|
chatContext.handleSend = handleSend;
|
||||||
|
|
||||||
function handleSend(newMessage?: string) {
|
function handleSend(newMessage?: string) {
|
||||||
// 将富文本信息转换成纯文本信息
|
// 将富文本信息转换成纯文本信息
|
||||||
@ -77,11 +80,7 @@ function handleSend(newMessage?: string) {
|
|||||||
|
|
||||||
loop.registerOnError((error) => {
|
loop.registerOnError((error) => {
|
||||||
|
|
||||||
ElMessage({
|
ElMessage.error(error.msg);
|
||||||
message: error.msg,
|
|
||||||
type: 'error',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error.state === MessageState.ReceiveChunkError) {
|
if (error.state === MessageState.ReceiveChunkError) {
|
||||||
tabStorage.messages.push({
|
tabStorage.messages.push({
|
||||||
@ -125,8 +124,6 @@ function handleAbort() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provide('handleSend', handleSend);
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateScrollHeight();
|
updateScrollHeight();
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
class="rich-editor"
|
class="rich-editor"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
|
@paste="handlePaste"
|
||||||
@keydown.backspace="handleBackspace"
|
@keydown.backspace="handleBackspace"
|
||||||
@keydown.enter="handleKeydown"
|
@keydown.enter="handleKeydown"
|
||||||
@compositionstart="handleCompositionStart"
|
@compositionstart="handleCompositionStart"
|
||||||
@ -171,6 +172,32 @@ function handleKeydown(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePaste(event: ClipboardEvent) {
|
||||||
|
event.preventDefault(); // 阻止默认粘贴行为
|
||||||
|
const clipboardData = event.clipboardData;
|
||||||
|
if (clipboardData) {
|
||||||
|
const pastedText = clipboardData.getData('text/plain');
|
||||||
|
const editorElement = editor.value;
|
||||||
|
if (editorElement instanceof HTMLDivElement) {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection && selection.rangeCount > 0) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
const textNode = document.createTextNode(pastedText);
|
||||||
|
range.insertNode(textNode);
|
||||||
|
range.setStartAfter(textNode);
|
||||||
|
range.collapse(true);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.value) {
|
||||||
|
editor.value.dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleCompositionStart() {
|
function handleCompositionStart() {
|
||||||
isComposing.value = true;
|
isComposing.value = true;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ 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 };
|
||||||
interface TaskLoopOptions {
|
interface TaskLoopOptions {
|
||||||
maxEpochs: number;
|
maxEpochs: number;
|
||||||
|
maxJsonParseRetry: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IErrorMssage {
|
interface IErrorMssage {
|
||||||
@ -19,6 +20,10 @@ interface IErrorMssage {
|
|||||||
msg: string
|
msg: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IDoConversationResult {
|
||||||
|
stop: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 对任务循环进行的抽象封装
|
* @description 对任务循环进行的抽象封装
|
||||||
*/
|
*/
|
||||||
@ -34,15 +39,19 @@ export class TaskLoop {
|
|||||||
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {},
|
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {},
|
||||||
private onDone: () => void = () => {},
|
private onDone: () => void = () => {},
|
||||||
private onEpoch: () => void = () => {},
|
private onEpoch: () => void = () => {},
|
||||||
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20 },
|
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3 },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleToolCalls(toolCalls: ToolCall[]) {
|
private async handleToolCalls(toolCalls: ToolCall[]) {
|
||||||
// TODO: 调用多个工具并返回调用结果?
|
// TODO: 调用多个工具并返回调用结果?
|
||||||
|
|
||||||
const toolCall = toolCalls[0];
|
const toolCall = toolCalls[0];
|
||||||
|
|
||||||
|
console.log('debug toolcall');
|
||||||
|
console.log(toolCalls);
|
||||||
|
|
||||||
let toolName: string;
|
let toolName: string;
|
||||||
let toolArgs: Record<string, any>;
|
let toolArgs: Record<string, any>;
|
||||||
|
|
||||||
@ -131,15 +140,15 @@ export class TaskLoop {
|
|||||||
|
|
||||||
if (currentCall === undefined) {
|
if (currentCall === undefined) {
|
||||||
// 新的工具调用开始
|
// 新的工具调用开始
|
||||||
this.streamingToolCalls.value = [{
|
this.streamingToolCalls.value[toolCall.index] = {
|
||||||
id: toolCall.id,
|
id: toolCall.id,
|
||||||
index: 0,
|
index: toolCall.index,
|
||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: toolCall.function?.name || '',
|
name: toolCall.function?.name || '',
|
||||||
arguments: toolCall.function?.arguments || ''
|
arguments: toolCall.function?.arguments || ''
|
||||||
}
|
}
|
||||||
}];
|
};
|
||||||
} else {
|
} else {
|
||||||
// 累积现有工具调用的信息
|
// 累积现有工具调用的信息
|
||||||
if (currentCall) {
|
if (currentCall) {
|
||||||
@ -167,16 +176,9 @@ export class TaskLoop {
|
|||||||
|
|
||||||
private doConversation(chatData: ChatCompletionCreateParamsBase) {
|
private doConversation(chatData: ChatCompletionCreateParamsBase) {
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<IDoConversationResult>((resolve, reject) => {
|
||||||
const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => {
|
const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => {
|
||||||
if (data.code !== 200) {
|
// data.code 一定为 200,否则不会走这个 route
|
||||||
this.onError({
|
|
||||||
state: MessageState.ReceiveChunkError,
|
|
||||||
msg: data.msg || '请求模型服务时发生错误'
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { chunk } = data.msg as { chunk: ChatCompletionChunk };
|
const { chunk } = data.msg as { chunk: ChatCompletionChunk };
|
||||||
|
|
||||||
// 处理增量的 content 和 tool_calls
|
// 处理增量的 content 和 tool_calls
|
||||||
@ -187,11 +189,34 @@ export class TaskLoop {
|
|||||||
this.onChunk(chunk);
|
this.onChunk(chunk);
|
||||||
}, { once: false });
|
}, { once: false });
|
||||||
|
|
||||||
this.bridge.addCommandListener('llm/chat/completions/done', data => {
|
const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => {
|
||||||
this.onDone();
|
this.onDone();
|
||||||
chunkHandler();
|
chunkHandler();
|
||||||
|
errorHandler();
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
stop: false
|
||||||
|
});
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
console.log('register error handler');
|
||||||
|
|
||||||
|
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
|
||||||
|
|
||||||
|
console.log('enter error report');
|
||||||
|
|
||||||
|
this.onError({
|
||||||
|
state: MessageState.ReceiveChunkError,
|
||||||
|
msg: data.msg || '请求模型服务时发生错误'
|
||||||
|
});
|
||||||
|
|
||||||
|
chunkHandler();
|
||||||
|
doneHandler();
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
stop: true
|
||||||
|
});
|
||||||
|
|
||||||
resolve();
|
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
this.bridge.postMessage({
|
this.bridge.postMessage({
|
||||||
@ -273,6 +298,10 @@ export class TaskLoop {
|
|||||||
this.onEpoch = handler;
|
this.onEpoch = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setMaxEpochs(maxEpochs: number) {
|
||||||
|
this.taskOptions.maxEpochs = maxEpochs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 开启循环,异步更新 DOM
|
* @description 开启循环,异步更新 DOM
|
||||||
*/
|
*/
|
||||||
@ -288,6 +317,8 @@ export class TaskLoop {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let jsonParseErrorRetryCount = 0;
|
||||||
|
|
||||||
for (let i = 0; i < this.taskOptions.maxEpochs; ++ i) {
|
for (let i = 0; i < this.taskOptions.maxEpochs; ++ i) {
|
||||||
|
|
||||||
this.onEpoch();
|
this.onEpoch();
|
||||||
@ -308,7 +339,10 @@ export class TaskLoop {
|
|||||||
this.currentChatId = chatData.id!;
|
this.currentChatId = chatData.id!;
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
await this.doConversation(chatData);
|
const doConverationResult = await this.doConversation(chatData);
|
||||||
|
|
||||||
|
console.log(doConverationResult);
|
||||||
|
|
||||||
|
|
||||||
// 如果存在需要调度的工具
|
// 如果存在需要调度的工具
|
||||||
if (this.streamingToolCalls.value.length > 0) {
|
if (this.streamingToolCalls.value.length > 0) {
|
||||||
@ -333,11 +367,25 @@ export class TaskLoop {
|
|||||||
if (toolCallResult.state === MessageState.ParseJsonError) {
|
if (toolCallResult.state === MessageState.ParseJsonError) {
|
||||||
// 如果是因为解析 JSON 错误,则重新开始
|
// 如果是因为解析 JSON 错误,则重新开始
|
||||||
tabStorage.messages.pop();
|
tabStorage.messages.pop();
|
||||||
redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments);
|
jsonParseErrorRetryCount ++;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolCallResult.state === MessageState.Success) {
|
redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments);
|
||||||
|
|
||||||
|
// 如果因为 JSON 错误而失败太多,就只能中断了
|
||||||
|
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) {
|
||||||
const toolCall = this.streamingToolCalls.value[0];
|
const toolCall = this.streamingToolCalls.value[0];
|
||||||
|
|
||||||
tabStorage.messages.push({
|
tabStorage.messages.push({
|
||||||
@ -351,10 +399,7 @@ export class TaskLoop {
|
|||||||
usage: this.completionUsage
|
usage: this.completionUsage
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
} else if (toolCallResult.state === MessageState.ToolCall) {
|
||||||
|
|
||||||
|
|
||||||
if (toolCallResult.state === MessageState.ToolCall) {
|
|
||||||
const toolCall = this.streamingToolCalls.value[0];
|
const toolCall = this.streamingToolCalls.value[0];
|
||||||
|
|
||||||
tabStorage.messages.push({
|
tabStorage.messages.push({
|
||||||
@ -385,7 +430,11 @@ export class TaskLoop {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 一些提示
|
// 一些提示
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回答聚合完成后根据 stop 来决定是否提前中断
|
||||||
|
if (doConverationResult.stop) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,18 @@ export function makeUsageStatistic(extraInfo: IExtraInfo): UsageStatistic | unde
|
|||||||
total: usage.prompt_tokens + usage.completion_tokens,
|
total: usage.prompt_tokens + usage.completion_tokens,
|
||||||
cacheHitRatio: Math.ceil(usage.prompt_tokens_details?.cached_tokens || 0 / usage.prompt_tokens * 1000) / 10,
|
cacheHitRatio: Math.ceil(usage.prompt_tokens_details?.cached_tokens || 0 / usage.prompt_tokens * 1000) / 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (usage.prompt_tokens && usage.completion_tokens) {
|
||||||
|
return {
|
||||||
|
input: usage.prompt_tokens,
|
||||||
|
output: usage.completion_tokens,
|
||||||
|
total: usage.prompt_tokens + usage.completion_tokens,
|
||||||
|
cacheHitRatio: Math.ceil((usage.prompt_tokens_details?.cached_tokens || 0) / usage.prompt_tokens * 1000) / 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -165,6 +165,11 @@ provide('streamingToolCalls', streamingToolCalls);
|
|||||||
provide('isLoading', isLoading);
|
provide('isLoading', isLoading);
|
||||||
provide('autoScroll', autoScroll);
|
provide('autoScroll', autoScroll);
|
||||||
|
|
||||||
|
const chatContext = {
|
||||||
|
handleSend: undefined
|
||||||
|
};
|
||||||
|
provide('chatContext', chatContext);
|
||||||
|
|
||||||
// 修改 scrollToBottom 方法
|
// 修改 scrollToBottom 方法
|
||||||
async function scrollToBottom() {
|
async function scrollToBottom() {
|
||||||
if (!scrollbarRef.value || !messageListRef.value) return;
|
if (!scrollbarRef.value || !messageListRef.value) return;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="message-role">Agent</div>
|
<div class="message-role">Agent</div>
|
||||||
<div class="message-text">
|
<div class="message-text">
|
||||||
<div v-if="message.content" v-html="markdownToHtml(props.message.content)"></div>
|
<div v-if="message.content" v-html="markdownToHtml(messageContent)"></div>
|
||||||
</div>
|
</div>
|
||||||
<MessageMeta :message="props.message" />
|
<MessageMeta :message="props.message" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps } from 'vue';
|
import { computed, defineProps } from 'vue';
|
||||||
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
||||||
|
|
||||||
import MessageMeta from './message-meta.vue';
|
import MessageMeta from './message-meta.vue';
|
||||||
@ -23,6 +23,17 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const messageContent = computed(() => {
|
||||||
|
if (typeof props.message.content === 'undefined') {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
if (typeof props.message.content === 'object') {
|
||||||
|
return JSON.stringify(props.message.content, null, 2);
|
||||||
|
}
|
||||||
|
return props.message.content.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="message-avatar">
|
<div class="message-avatar streaming-box">
|
||||||
<span class="iconfont icon-chat"></span>
|
<span class="iconfont icon-chat"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-text">
|
<div class="message-text streaming-box">
|
||||||
<span v-html="waitingMarkdownToHtml(streamingContent)"></span>
|
<span v-html="waitingMarkdownToHtml(streamingContent)"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,11 +38,11 @@
|
|||||||
<div class="tool-call-header result">
|
<div class="tool-call-header result">
|
||||||
<span class="tool-name">
|
<span class="tool-name">
|
||||||
<span :class="`iconfont icon-${currentMessageLevel}`"></span>
|
<span :class="`iconfont icon-${currentMessageLevel}`"></span>
|
||||||
{{ isValid ? '响应': '错误' }}
|
{{ isValid ? t("response") : t('error') }}
|
||||||
<el-button v-if="!isValid" size="small"
|
<el-button v-if="!isValid" size="small"
|
||||||
@click="gotoIssue()"
|
@click="gotoIssue()"
|
||||||
>
|
>
|
||||||
反馈
|
{{ 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'">
|
||||||
@ -82,7 +82,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else style="width: 90%">
|
||||||
|
<div class="tool-call-header result">
|
||||||
|
<span class="tool-name">
|
||||||
|
<span :class="`iconfont icon-waiting`"></span>
|
||||||
|
{{ t('waiting-mcp-server') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-result-content">
|
||||||
|
<div class="progress">
|
||||||
|
<el-progress
|
||||||
|
:percentage="100"
|
||||||
|
:format="() => ''"
|
||||||
|
:indeterminate="true"
|
||||||
|
text-inside
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MessageMeta :message="message" />
|
<MessageMeta :message="message" />
|
||||||
|
|
||||||
@ -93,7 +110,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue';
|
import { defineProps, ref, watch, PropType, computed, defineEmits, inject, Ref } from 'vue';
|
||||||
|
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';
|
||||||
@ -103,6 +121,8 @@ import { ToolCallContent } from '@/hook/type';
|
|||||||
|
|
||||||
import ToolcallResultItem from './toolcall-result-item.vue';
|
import ToolcallResultItem from './toolcall-result-item.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message: {
|
message: {
|
||||||
type: Object as PropType<IRenderMessage>,
|
type: Object as PropType<IRenderMessage>,
|
||||||
@ -183,7 +203,14 @@ const isValid = computed(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const currentMessageLevel = computed(() => {
|
const currentMessageLevel = computed(() => {
|
||||||
|
|
||||||
|
// 此时正在等待 mcp server 给出回应
|
||||||
|
if (!props.message.toolResult) {
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
|
||||||
if (!isValid.value) {
|
if (!isValid.value) {
|
||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
@ -223,6 +250,16 @@ function updateToolCallResultItem(value: any, index: number) {
|
|||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-result-content .el-progress-bar__outer {
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-result-content .progress {
|
||||||
|
border-radius: .5em;
|
||||||
|
background-color: var(--el-fill-color-light) !important;
|
||||||
|
padding: 20px 10px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.message-text.tool_calls.warning {
|
.message-text.tool_calls.warning {
|
||||||
border: 1px solid var(--el-color-warning);
|
border: 1px solid var(--el-color-warning);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, ref, PropType, inject } from 'vue';
|
import { defineProps, ref, PropType, inject } from 'vue';
|
||||||
import { tabs } from '../../panel';
|
import { tabs } from '../../panel';
|
||||||
import { ChatStorage, IRenderMessage } from '../chat';
|
import type { ChatStorage, IRenderMessage } from '../chat-box/chat';
|
||||||
|
|
||||||
import KCuteTextarea from '@/components/k-cute-textarea/index.vue';
|
import KCuteTextarea from '@/components/k-cute-textarea/index.vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
@ -58,7 +58,7 @@ const tabStorage = tab.storage as ChatStorage;
|
|||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
const userInput = ref('');
|
const userInput = ref('');
|
||||||
|
|
||||||
const handleSend = inject<(newMessage: string | undefined) => void>('handleSend');
|
const chatContext = inject('chatContext') as any;
|
||||||
|
|
||||||
const toggleEdit = () => {
|
const toggleEdit = () => {
|
||||||
isEditing.value = !isEditing.value;
|
isEditing.value = !isEditing.value;
|
||||||
@ -70,10 +70,12 @@ const toggleEdit = () => {
|
|||||||
const handleKeydown = (event: KeyboardEvent) => {
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
const index = tabStorage.messages.findIndex(msg => msg.extraInfo === props.message.extraInfo);
|
const index = tabStorage.messages.findIndex(msg => msg.extraInfo === props.message.extraInfo);
|
||||||
|
|
||||||
if (index !== -1 && handleSend) {
|
console.log(chatContext);
|
||||||
|
|
||||||
|
if (index !== -1 && chatContext.handleSend) {
|
||||||
// 把 index 之后的全部删除(包括 index)
|
// 把 index 之后的全部删除(包括 index)
|
||||||
tabStorage.messages.splice(index);
|
tabStorage.messages.splice(index);
|
||||||
handleSend(userInput.value);
|
chatContext.handleSend(userInput.value);
|
||||||
|
|
||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
}
|
}
|
||||||
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "اختر الإعداد المسبق",
|
"choose-presetting": "اختر الإعداد المسبق",
|
||||||
"cwd": "دليل التنفيذ",
|
"cwd": "دليل التنفيذ",
|
||||||
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP",
|
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP",
|
||||||
"return": "عودة"
|
"return": "عودة",
|
||||||
|
"error": "خطأ",
|
||||||
|
"feedback": "تعليقات",
|
||||||
|
"waiting-mcp-server": "في انتظار استجابة خادم MCP"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "Voreinstellung auswählen",
|
"choose-presetting": "Voreinstellung auswählen",
|
||||||
"cwd": "Ausführungsverzeichnis",
|
"cwd": "Ausführungsverzeichnis",
|
||||||
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools",
|
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools",
|
||||||
"return": "Zurück"
|
"return": "Zurück",
|
||||||
|
"error": "Fehler",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"waiting-mcp-server": "Warten auf Antwort vom MCP-Server"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "Select preset",
|
"choose-presetting": "Select preset",
|
||||||
"cwd": "Execution directory",
|
"cwd": "Execution directory",
|
||||||
"mcp-server-timeout": "Maximum call time of MCP tool",
|
"mcp-server-timeout": "Maximum call time of MCP tool",
|
||||||
"return": "Back"
|
"return": "Back",
|
||||||
|
"error": "Error",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"waiting-mcp-server": "Waiting for MCP server response"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "Sélectionner un préréglage",
|
"choose-presetting": "Sélectionner un préréglage",
|
||||||
"cwd": "Répertoire d'exécution",
|
"cwd": "Répertoire d'exécution",
|
||||||
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
|
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
|
||||||
"return": "Retour"
|
"return": "Retour",
|
||||||
|
"error": "Erreur",
|
||||||
|
"feedback": "Retour",
|
||||||
|
"waiting-mcp-server": "En attente de la réponse du serveur MCP"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "プリセットを選択",
|
"choose-presetting": "プリセットを選択",
|
||||||
"cwd": "実行ディレクトリ",
|
"cwd": "実行ディレクトリ",
|
||||||
"mcp-server-timeout": "MCPツールの最大呼び出し時間",
|
"mcp-server-timeout": "MCPツールの最大呼び出し時間",
|
||||||
"return": "戻る"
|
"return": "戻る",
|
||||||
|
"error": "エラー",
|
||||||
|
"feedback": "フィードバック",
|
||||||
|
"waiting-mcp-server": "MCPサーバーの応答を待機中"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "프리셋 선택",
|
"choose-presetting": "프리셋 선택",
|
||||||
"cwd": "실행 디렉터리",
|
"cwd": "실행 디렉터리",
|
||||||
"mcp-server-timeout": "MCP 도구 최대 호출 시간",
|
"mcp-server-timeout": "MCP 도구 최대 호출 시간",
|
||||||
"return": "돌아가기"
|
"return": "돌아가기",
|
||||||
|
"error": "오류",
|
||||||
|
"feedback": "피드백",
|
||||||
|
"waiting-mcp-server": "MCP 서버 응답 대기 중"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "Выбрать预设",
|
"choose-presetting": "Выбрать预设",
|
||||||
"cwd": "Каталог выполнения",
|
"cwd": "Каталог выполнения",
|
||||||
"mcp-server-timeout": "Максимальное время вызова инструмента MCP",
|
"mcp-server-timeout": "Максимальное время вызова инструмента MCP",
|
||||||
"return": "Назад"
|
"return": "Назад",
|
||||||
|
"error": "Ошибка",
|
||||||
|
"feedback": "Обратная связь",
|
||||||
|
"waiting-mcp-server": "Ожидание ответа от сервера MCP"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "选择预设",
|
"choose-presetting": "选择预设",
|
||||||
"cwd": "执行目录",
|
"cwd": "执行目录",
|
||||||
"mcp-server-timeout": "MCP工具最长调用时间",
|
"mcp-server-timeout": "MCP工具最长调用时间",
|
||||||
"return": "返回"
|
"return": "返回",
|
||||||
|
"error": "错误",
|
||||||
|
"feedback": "反馈",
|
||||||
|
"waiting-mcp-server": "等待 MCP 服务器响应"
|
||||||
}
|
}
|
@ -152,5 +152,8 @@
|
|||||||
"choose-presetting": "選擇預設",
|
"choose-presetting": "選擇預設",
|
||||||
"cwd": "執行目錄",
|
"cwd": "執行目錄",
|
||||||
"mcp-server-timeout": "MCP工具最長調用時間",
|
"mcp-server-timeout": "MCP工具最長調用時間",
|
||||||
"return": "返回"
|
"return": "返回",
|
||||||
|
"error": "錯誤",
|
||||||
|
"feedback": "反饋",
|
||||||
|
"waiting-mcp-server": "等待MCP伺服器響應"
|
||||||
}
|
}
|
@ -6,7 +6,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
OpenMCP Client 0.0.7 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
OpenMCP Client 0.0.8 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -2,22 +2,26 @@ import { ChatStorage } from '@/components/main-panel/chat/chat-box/chat';
|
|||||||
import { TaskLoop } from '@/components/main-panel/chat/core/task-loop';
|
import { TaskLoop } from '@/components/main-panel/chat/core/task-loop';
|
||||||
import { llmManager } from './llm';
|
import { llmManager } from './llm';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
import { makeUsageStatistic } from '@/components/main-panel/chat/core/usage';
|
||||||
|
|
||||||
export const llmSettingRef = ref<any>(null);
|
export const llmSettingRef = ref<any>(null);
|
||||||
|
|
||||||
export const simpleTestResult = reactive<{
|
export const simpleTestResult = reactive<{
|
||||||
done: boolean,
|
done: boolean,
|
||||||
start: boolean,
|
start: boolean,
|
||||||
error: any
|
error: any,
|
||||||
|
tps: string | number | undefined
|
||||||
}>({
|
}>({
|
||||||
done: false,
|
done: false,
|
||||||
start: false,
|
start: false,
|
||||||
error: '',
|
error: '',
|
||||||
|
tps: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
export function makeSimpleTalk() {
|
export async function makeSimpleTalk() {
|
||||||
simpleTestResult.done = false;
|
simpleTestResult.done = false;
|
||||||
simpleTestResult.start = true;
|
simpleTestResult.start = true;
|
||||||
|
simpleTestResult.tps = undefined;
|
||||||
|
|
||||||
// 使用最简单的 hello 来测试
|
// 使用最简单的 hello 来测试
|
||||||
const testMessage = 'hello';
|
const testMessage = 'hello';
|
||||||
@ -38,18 +42,35 @@ export function makeSimpleTalk() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loop.setMaxEpochs(1);
|
||||||
|
|
||||||
loop.registerOnDone(() => {
|
loop.registerOnDone(() => {
|
||||||
console.log('done');
|
|
||||||
simpleTestResult.error = '';
|
simpleTestResult.error = '';
|
||||||
simpleTestResult.done = true;
|
simpleTestResult.done = true;
|
||||||
simpleTestResult.start = false;
|
simpleTestResult.start = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
loop.registerOnError(error => {
|
loop.registerOnError(error => {
|
||||||
console.log(error);
|
const errorReason = error.msg;
|
||||||
simpleTestResult.error = error;
|
const errorText = JSON.stringify(errorReason);
|
||||||
|
|
||||||
|
simpleTestResult.error = errorText;
|
||||||
simpleTestResult.start = false;
|
simpleTestResult.start = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
loop.start(chatStorage, testMessage);
|
const startTime = performance.now();
|
||||||
|
await loop.start(chatStorage, testMessage);
|
||||||
|
|
||||||
|
const costTime = (performance.now() - startTime!) / 1000;
|
||||||
|
const message = chatStorage.messages.at(-1);
|
||||||
|
console.log(chatStorage.messages);
|
||||||
|
|
||||||
|
if (message?.extraInfo) {
|
||||||
|
const usage = message.extraInfo.usage;
|
||||||
|
if (usage?.prompt_tokens && usage.completion_tokens) {
|
||||||
|
const total = usage?.prompt_tokens + usage?.completion_tokens;
|
||||||
|
simpleTestResult.tps = (total / costTime).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 根据不同模型展示不同的接入点 -->
|
<!-- TODO: 根据不同模型展示不同的接入点 -->
|
||||||
<div v-if="false">
|
<div v-if="false">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +80,7 @@
|
|||||||
<ConnectTest />
|
<ConnectTest />
|
||||||
|
|
||||||
<!-- 当前页面的聊天框 -->
|
<!-- 当前页面的聊天框 -->
|
||||||
<el-dialog v-model="dialogVisible" width="50%" style="min-width: 500px; max-width: 800px;padding: 20px;">
|
<el-dialog v-model="dialogVisible" width="50%" class="api-man-dialog">
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@ -286,6 +286,17 @@ function handleCommand(command: {type: string, index: number}) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
.api-man-dialog {
|
||||||
|
min-width: 500px;
|
||||||
|
max-width: 800px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-man-dialog .el-tag {
|
||||||
|
background-color: var(--main-light-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-save-container {
|
.setting-save-container {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,25 @@
|
|||||||
<div class="connect-test" v-if="simpleTestResult.done || simpleTestResult.error">
|
<div class="connect-test" v-if="simpleTestResult.done || simpleTestResult.error">
|
||||||
<div class="test-result">
|
<div class="test-result">
|
||||||
<div class="result-item" v-if="simpleTestResult.done">
|
<div class="result-item" v-if="simpleTestResult.done">
|
||||||
<span class="iconfont icon-success"></span>
|
<span class="iconfont icon-dui"></span>
|
||||||
<span>{{ "✅ okey dockey :D" }}</span>
|
<span>{{ " okey dockey :D" }}</span>
|
||||||
|
<span v-if="simpleTestResult.tps" class="tps">{{ simpleTestResult.tps }} token/s</span>
|
||||||
|
<span v-else class="tps">{{ t("server-not-support-statistic") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="result-item error" v-if="simpleTestResult.error">
|
<div class="result-item error" v-if="simpleTestResult.error">
|
||||||
<span class="iconfont icon-error"></span>
|
<span class="iconfont icon-cuo"></span>
|
||||||
<span>{{ '❌ ' + simpleTestResult.error }}</span>
|
<span>{{ ' ' + simpleTestResult.error }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { simpleTestResult } from './api';
|
import { simpleTestResult } from './api';
|
||||||
|
|
||||||
defineComponent({ name: 'connect-test' });
|
const { t } = useI18n();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -43,6 +46,14 @@ defineComponent({ name: 'connect-test' });
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connect-test .tps {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: var(--foreground);
|
||||||
|
background-color: var(--el-fill-color-light);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.result-item.error {
|
.result-item.error {
|
||||||
color: var(--el-color-danger);
|
color: var(--el-color-danger);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,20 @@ export class LlmController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
await streamingChatCompletion(data, webview);
|
await streamingChatCompletion(data, webview);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error' + error);
|
||||||
|
|
||||||
|
webview.postMessage({
|
||||||
|
command: 'llm/chat/completions/error',
|
||||||
|
data: {
|
||||||
|
msg: error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: -1,
|
code: -1,
|
||||||
|
@ -58,9 +58,6 @@ export async function streamingChatCompletion(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chunk);
|
|
||||||
|
|
||||||
|
|
||||||
if (chunk.choices) {
|
if (chunk.choices) {
|
||||||
const chunkResult = {
|
const chunkResult = {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user