Compare commits
5 Commits
0f947c5b09
...
e20bbf2b42
Author | SHA1 | Date | |
---|---|---|---|
e20bbf2b42 | |||
1fef3a1150 | |||
68f45fedf7 | |||
4c0566b470 | |||
da482e26a9 |
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
||||
# Change Log
|
||||
|
||||
## [main] 0.0.8
|
||||
- 大模型 API 测试时更加完整的报错
|
||||
- 修复 0.0.7 引入的bug:修改对话无法发出
|
||||
- 修复 bug:富文本编辑器粘贴文本会带样式
|
||||
- 修复 bug:富文本编辑器发送前缀为空的字符会全部为空
|
||||
- 修复 bug:流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败
|
||||
- 修复 bug:大模型返回大量重复错误信息
|
||||
- 新特性:支持一次对话同时调用多个工具
|
||||
- UI:优化代码高亮的滚动条
|
||||
- 新特性:resources/list 协议的内容点击就会直接渲染,无需二次发送
|
||||
- 新特性:resources prompts tools 的结果的 json 模式支持高亮
|
||||
|
||||
## [main] 0.0.7
|
||||
- 优化页面布局,使得调试窗口可以显示更多内容
|
||||
- 扩大默认的上下文长度 10 -> 20
|
||||
|
@ -30,4 +30,9 @@ New-Item -ItemType Directory -Path ./software/openmcp-sdk -Force
|
||||
Remove-Item -Recurse -Force ./software/openmcp-sdk/* -ErrorAction SilentlyContinue
|
||||
Copy-Item -Recurse -Path ./openmcp-sdk -Destination ./software/ -Force
|
||||
|
||||
$serviceJob = Start-Job -ScriptBlock {
|
||||
param($workDir)
|
||||
npm run build:task-loop
|
||||
} -ArgumentList $currentDir
|
||||
|
||||
Write-Output "finish building services in ./openmcp-sdk"
|
||||
|
@ -13,4 +13,6 @@ mkdir -p ./software/openmcp-sdk
|
||||
rm -rf ./software/openmcp-sdk
|
||||
cp -r ./openmcp-sdk ./software/
|
||||
|
||||
npm run build:task-loop
|
||||
|
||||
echo "finish building services in ./openmcp-sdk"
|
||||
|
154
package-lock.json
generated
154
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openmcp",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openmcp",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
@ -24,6 +24,7 @@
|
||||
"@types/showdown": "^2.0.0",
|
||||
"@types/vscode": "^1.72.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"null-loader": "^4.0.1",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.4.2",
|
||||
"webpack": "^5.99.5",
|
||||
@ -570,6 +571,16 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
@ -980,6 +991,16 @@
|
||||
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@ -1266,6 +1287,13 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
@ -1870,6 +1898,19 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@ -1897,6 +1938,21 @@
|
||||
"node": ">=6.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/localforage": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||
@ -2064,6 +2120,80 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/null-loader": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz",
|
||||
"integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/null-loader/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/null-loader/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/null-loader/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/null-loader/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -2305,6 +2435,16 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
@ -3037,6 +3177,16 @@
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "openmcp",
|
||||
"displayName": "OpenMCP",
|
||||
"description": "An all in one MCP Client/TestTool",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"publisher": "kirigaya",
|
||||
"author": {
|
||||
"name": "kirigaya",
|
||||
@ -219,7 +219,8 @@
|
||||
"pretest": "npm run compile && npm run lint",
|
||||
"lint": "eslint src --ext ts",
|
||||
"test": "node ./out/test/runTest.js",
|
||||
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js"
|
||||
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js",
|
||||
"build:task-loop": "webpack --config webpack/webpack.task-loop.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
@ -238,6 +239,7 @@
|
||||
"@types/showdown": "^2.0.0",
|
||||
"@types/vscode": "^1.72.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"null-loader": "^4.0.1",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.4.2",
|
||||
"webpack": "^5.99.5",
|
||||
|
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4870215 */
|
||||
src: url('iconfont.woff2?t=1746529081655') format('woff2'),
|
||||
url('iconfont.woff?t=1746529081655') format('woff'),
|
||||
url('iconfont.ttf?t=1746529081655') format('truetype');
|
||||
src: url('iconfont.woff2?t=1746703816245') format('woff2'),
|
||||
url('iconfont.woff?t=1746703816245') format('woff'),
|
||||
url('iconfont.ttf?t=1746703816245') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-waiting:before {
|
||||
content: "\e6d0";
|
||||
}
|
||||
|
||||
.icon-timeout:before {
|
||||
content: "\edf5";
|
||||
}
|
||||
|
Binary file not shown.
@ -131,6 +131,10 @@ a {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.tool-arguments .openmcp-code-block pre code::-webkit-scrollbar {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.tool-arguments .openmcp-code-block pre code {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { pinkLog, redLog } from '@/views/setting/util';
|
||||
import { acquireVsCodeApi, electronApi, getPlatform } from './platform';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export interface VSCodeMessage {
|
||||
command: string;
|
||||
@ -20,7 +19,7 @@ interface AddCommandListenerOption {
|
||||
once: boolean // 只调用一次就销毁
|
||||
}
|
||||
|
||||
class MessageBridge {
|
||||
export class MessageBridge {
|
||||
private ws: WebSocket | null = null;
|
||||
private handlers = new Map<string, Set<CommandHandler>>();
|
||||
private isConnected: Promise<boolean> | null = null;
|
||||
@ -44,6 +43,11 @@ class MessageBridge {
|
||||
pinkLog('当前模式: electron');
|
||||
break;
|
||||
|
||||
case 'nodejs':
|
||||
this.setupNodejsListener();
|
||||
pinkLog('当前模式: nodejs');
|
||||
break;
|
||||
|
||||
case 'web':
|
||||
this.setupWebSocket();
|
||||
pinkLog('当前模式: web');
|
||||
@ -114,6 +118,17 @@ class MessageBridge {
|
||||
};
|
||||
}
|
||||
|
||||
private setupNodejsListener() {
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
|
||||
this.postMessage = (message) => {
|
||||
eventEmitter.emit('server', message);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 对 message 发起调度,根据 command 类型获取调取器
|
||||
* @param message
|
||||
|
@ -1,14 +1,16 @@
|
||||
export type OpenMcpSupportPlatform = 'web' | 'vscode' | 'electron';
|
||||
export type OpenMcpSupportPlatform = 'web' | 'vscode' | 'electron' | 'nodejs';
|
||||
|
||||
export const acquireVsCodeApi = (window as any)['acquireVsCodeApi'];
|
||||
|
||||
export const electronApi = (window as any)['electronApi'];
|
||||
export const isNodejs = (window as any)['nodejs'];
|
||||
|
||||
export function getPlatform(): OpenMcpSupportPlatform {
|
||||
if (typeof acquireVsCodeApi !== 'undefined') {
|
||||
if (acquireVsCodeApi) {
|
||||
return 'vscode';
|
||||
} else if (typeof electronApi !== 'undefined') {
|
||||
} else if (electronApi) {
|
||||
return 'electron';
|
||||
} else if (isNodejs) {
|
||||
return 'nodejs';
|
||||
} else {
|
||||
return 'web';
|
||||
}
|
||||
|
22
renderer/src/components/json-render/index.vue
Normal file
22
renderer/src/components/json-render/index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<el-scrollbar width="100%">
|
||||
<div v-html="renderJson(json)">
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, PropType } from 'vue';
|
||||
import { renderJson } from '../main-panel/chat/markdown/markdown';
|
||||
|
||||
const props = defineProps({
|
||||
json: {
|
||||
type: Object as PropType<string | object | undefined>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,4 +1,4 @@
|
||||
import { ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import type { ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import { Ref, ref } from "vue";
|
||||
|
||||
import type { OpenAI } from 'openai';
|
||||
@ -27,6 +27,7 @@ export interface IExtraInfo {
|
||||
|
||||
export interface ToolMessage {
|
||||
role: 'tool';
|
||||
index: number;
|
||||
content: ToolCallContent[];
|
||||
tool_call_id?: string
|
||||
name?: string // 工具名称,当 role 为 tool
|
||||
@ -95,15 +96,24 @@ export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
|
||||
|
||||
export const allTools = ref<ToolItem[]>([]);
|
||||
|
||||
export interface IRenderMessage {
|
||||
role: 'user' | 'assistant/content' | 'assistant/tool_calls' | 'tool';
|
||||
export interface ICommonRenderMessage {
|
||||
role: 'user' | 'assistant/content';
|
||||
content: string;
|
||||
toolResult?: ToolCallContent[];
|
||||
tool_calls?: ToolCall[];
|
||||
showJson?: Ref<boolean>;
|
||||
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[]) {
|
||||
const toolsSchema = [];
|
||||
for (let i = 0; i < enableTools.length; i++) {
|
||||
|
@ -61,6 +61,26 @@ const streamingContent = inject('streamingContent') as Ref<string>;
|
||||
const streamingToolCalls = inject('streamingToolCalls') as Ref<ToolCall[]>;
|
||||
const scrollToBottom = inject('scrollToBottom') as () => Promise<void>;
|
||||
const updateScrollHeight = inject('updateScrollHeight') as () => void;
|
||||
const chatContext = inject('chatContext') as any;
|
||||
|
||||
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) {
|
||||
// 将富文本信息转换成纯文本信息
|
||||
@ -76,17 +96,16 @@ function handleSend(newMessage?: string) {
|
||||
loop = new TaskLoop(streamingContent, streamingToolCalls);
|
||||
|
||||
loop.registerOnError((error) => {
|
||||
console.log('error.msg');
|
||||
console.log(error.msg);
|
||||
|
||||
ElMessage({
|
||||
message: error.msg,
|
||||
type: 'error',
|
||||
duration: 3000
|
||||
});
|
||||
const errorMessage = clearErrorMessage(error.msg);
|
||||
ElMessage.error(errorMessage);
|
||||
|
||||
if (error.state === MessageState.ReceiveChunkError) {
|
||||
tabStorage.messages.push({
|
||||
role: 'assistant',
|
||||
content: error.msg,
|
||||
content: errorMessage,
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: error.state,
|
||||
@ -125,8 +144,6 @@ function handleAbort() {
|
||||
}
|
||||
}
|
||||
|
||||
provide('handleSend', handleSend);
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
updateScrollHeight();
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<el-dialog v-model="showChooseResource" :title="t('resources')" width="400px">
|
||||
<div class="resource-template-container-scrollbar" v-if="!selectResource">
|
||||
<ResourceList :tab-id="-1" @resource-selected="resource => selectResource = resource" />
|
||||
<ResourceList :tab-id="-1" @resource-selected="resource => handleResourceSelected(resource)" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<ResourceReader :tab-id="-1" :current-resource-name="selectResource!.name"
|
||||
@ -25,13 +25,14 @@
|
||||
import { createApp, inject, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ChatStorage, EditorContext } from '../chat';
|
||||
import { ResourcesReadResponse, ResourceTemplate } from '@/hook/type';
|
||||
import { Resources, ResourcesReadResponse, ResourceTemplate } from '@/hook/type';
|
||||
|
||||
import ResourceList from '@/components/main-panel/resource/resource-list.vue';
|
||||
import ResourceReader from '@/components/main-panel/resource/resouce-reader.vue';
|
||||
import { ElMessage, ElTooltip, ElProgress, ElPopover } from 'element-plus';
|
||||
|
||||
import ResourceChatItem from '../resource-chat-item.vue';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -57,6 +58,15 @@ function saveCursorPosition() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResourceSelected(resource: Resources) {
|
||||
selectResource.value = undefined;
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: resource.uri });
|
||||
if (msg) {
|
||||
await whenGetResourceResponse(msg as ResourcesReadResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async function whenGetResourceResponse(msg: ResourcesReadResponse) {
|
||||
if (!msg) {
|
||||
return;
|
||||
|
@ -10,6 +10,7 @@
|
||||
class="rich-editor"
|
||||
:placeholder="placeholder"
|
||||
@input="handleInput"
|
||||
@paste="handlePaste"
|
||||
@keydown.backspace="handleBackspace"
|
||||
@keydown.enter="handleKeydown"
|
||||
@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() {
|
||||
isComposing.value = true;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,24 +1,30 @@
|
||||
/* eslint-disable */
|
||||
import { Ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import { ToolCall, ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
|
||||
import { useMessageBridge } from "@/api/message-bridge";
|
||||
import type { OpenAI } from 'openai';
|
||||
import { callTool } from "../../tool/tools";
|
||||
import { llmManager, llms } from "@/views/setting/llm";
|
||||
import { pinkLog, redLog } from "@/views/setting/util";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { handleToolCalls } from "./handle-tool-calls";
|
||||
|
||||
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
||||
interface TaskLoopOptions {
|
||||
export interface TaskLoopOptions {
|
||||
maxEpochs: number;
|
||||
maxJsonParseRetry: number;
|
||||
}
|
||||
|
||||
interface IErrorMssage {
|
||||
export interface IErrorMssage {
|
||||
state: MessageState,
|
||||
msg: string
|
||||
}
|
||||
|
||||
export interface IDoConversationResult {
|
||||
stop: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 对任务循环进行的抽象封装
|
||||
*/
|
||||
@ -26,6 +32,7 @@ export class TaskLoop {
|
||||
private bridge = useMessageBridge();
|
||||
private currentChatId = '';
|
||||
private completionUsage: ChatCompletionChunk['usage'] | undefined;
|
||||
private llmConfig: any;
|
||||
|
||||
constructor(
|
||||
private readonly streamingContent: Ref<string>,
|
||||
@ -34,88 +41,11 @@ export class TaskLoop {
|
||||
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {},
|
||||
private onDone: () => void = () => {},
|
||||
private onEpoch: () => void = () => {},
|
||||
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20 },
|
||||
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3 },
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private async handleToolCalls(toolCalls: ToolCall[]) {
|
||||
// TODO: 调用多个工具并返回调用结果?
|
||||
const toolCall = toolCalls[0];
|
||||
|
||||
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) {
|
||||
const content = chunk.choices[0]?.delta?.content || '';
|
||||
if (content) {
|
||||
@ -131,15 +61,15 @@ export class TaskLoop {
|
||||
|
||||
if (currentCall === undefined) {
|
||||
// 新的工具调用开始
|
||||
this.streamingToolCalls.value = [{
|
||||
this.streamingToolCalls.value[toolCall.index] = {
|
||||
id: toolCall.id,
|
||||
index: 0,
|
||||
index: toolCall.index,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: toolCall.function?.name || '',
|
||||
arguments: toolCall.function?.arguments || ''
|
||||
}
|
||||
}];
|
||||
};
|
||||
} else {
|
||||
// 累积现有工具调用的信息
|
||||
if (currentCall) {
|
||||
@ -167,16 +97,9 @@ export class TaskLoop {
|
||||
|
||||
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 => {
|
||||
if (data.code !== 200) {
|
||||
this.onError({
|
||||
state: MessageState.ReceiveChunkError,
|
||||
msg: data.msg || '请求模型服务时发生错误'
|
||||
});
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// data.code 一定为 200,否则不会走这个 route
|
||||
const { chunk } = data.msg as { chunk: ChatCompletionChunk };
|
||||
|
||||
// 处理增量的 content 和 tool_calls
|
||||
@ -187,13 +110,33 @@ export class TaskLoop {
|
||||
this.onChunk(chunk);
|
||||
}, { once: false });
|
||||
|
||||
this.bridge.addCommandListener('llm/chat/completions/done', data => {
|
||||
const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => {
|
||||
this.onDone();
|
||||
chunkHandler();
|
||||
errorHandler();
|
||||
|
||||
resolve();
|
||||
resolve({
|
||||
stop: false
|
||||
});
|
||||
}, { once: true });
|
||||
|
||||
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
|
||||
this.onError({
|
||||
state: MessageState.ReceiveChunkError,
|
||||
msg: data.msg || '请求模型服务时发生错误'
|
||||
});
|
||||
|
||||
chunkHandler();
|
||||
doneHandler();
|
||||
|
||||
resolve({
|
||||
stop: true
|
||||
});
|
||||
|
||||
}, { once: true });
|
||||
|
||||
console.log(chatData);
|
||||
|
||||
this.bridge.postMessage({
|
||||
command: 'llm/chat/completions',
|
||||
data: JSON.parse(JSON.stringify(chatData)),
|
||||
@ -202,8 +145,8 @@ export class TaskLoop {
|
||||
}
|
||||
|
||||
public makeChatData(tabStorage: ChatStorage): ChatCompletionCreateParamsBase | undefined {
|
||||
const baseURL = llms[llmManager.currentModelIndex].baseUrl;
|
||||
const apiKey = llms[llmManager.currentModelIndex].userToken || '';
|
||||
const baseURL = this.getLlmConfig().baseUrl;
|
||||
const apiKey = this.getLlmConfig().userToken || '';
|
||||
|
||||
if (apiKey.trim() === '') {
|
||||
|
||||
@ -214,7 +157,7 @@ export class TaskLoop {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const model = llms[llmManager.currentModelIndex].userModel;
|
||||
const model = this.getLlmConfig().userModel;
|
||||
const temperature = tabStorage.settings.temperature;
|
||||
const tools = getToolSchema(tabStorage.settings.enableTools);
|
||||
|
||||
@ -273,6 +216,36 @@ export class TaskLoop {
|
||||
this.onEpoch = handler;
|
||||
}
|
||||
|
||||
public setMaxEpochs(maxEpochs: number) {
|
||||
this.taskOptions.maxEpochs = maxEpochs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置当前的 LLM 配置,用于 nodejs 环境运行
|
||||
* @param config
|
||||
* @example
|
||||
* setLlmConfig({
|
||||
* id: 'openai',
|
||||
* baseUrl: 'https://api.openai.com/v1',
|
||||
* userToken: 'sk-xxx',
|
||||
* userModel: 'gpt-3.5-turbo',
|
||||
* })
|
||||
*/
|
||||
public setLlmConfig(config: any) {
|
||||
this.llmConfig = config;
|
||||
}
|
||||
|
||||
public getLlmConfig() {
|
||||
if (this.llmConfig) {
|
||||
return this.llmConfig;
|
||||
}
|
||||
return llms[llmManager.currentModelIndex];
|
||||
}
|
||||
|
||||
public async connectToService() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 开启循环,异步更新 DOM
|
||||
*/
|
||||
@ -284,10 +257,12 @@ export class TaskLoop {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown'
|
||||
serverName: this.getLlmConfig().id || 'unknown'
|
||||
}
|
||||
});
|
||||
|
||||
let jsonParseErrorRetryCount = 0;
|
||||
|
||||
for (let i = 0; i < this.taskOptions.maxEpochs; ++ i) {
|
||||
|
||||
this.onEpoch();
|
||||
@ -308,7 +283,10 @@ export class TaskLoop {
|
||||
this.currentChatId = chatData.id!;
|
||||
|
||||
// 发送请求
|
||||
await this.doConversation(chatData);
|
||||
const doConverationResult = await this.doConversation(chatData);
|
||||
|
||||
console.log(doConverationResult);
|
||||
|
||||
|
||||
// 如果存在需要调度的工具
|
||||
if (this.streamingToolCalls.value.length > 0) {
|
||||
@ -320,54 +298,64 @@ export class TaskLoop {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown'
|
||||
serverName: this.getLlmConfig().id || 'unknown'
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
// 如果是因为解析 JSON 错误,则重新开始
|
||||
tabStorage.messages.pop();
|
||||
jsonParseErrorRetryCount ++;
|
||||
|
||||
if (toolCallResult.state === MessageState.ParseJsonError) {
|
||||
// 如果是因为解析 JSON 错误,则重新开始
|
||||
tabStorage.messages.pop();
|
||||
redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments);
|
||||
continue;
|
||||
}
|
||||
redLog('解析 JSON 错误 ' + toolCall?.function?.arguments);
|
||||
|
||||
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
|
||||
// 如果因为 JSON 错误而失败太多,就只能中断了
|
||||
if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) {
|
||||
tabStorage.messages.push({
|
||||
role: 'assistant',
|
||||
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`,
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: toolCallResult.state,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: undefined
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (toolCallResult.state === MessageState.Success) {
|
||||
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: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
}
|
||||
});
|
||||
} else if (toolCallResult.state === MessageState.ToolCall) {
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
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: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else if (this.streamingContent.value) {
|
||||
@ -377,7 +365,7 @@ export class TaskLoop {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
}
|
||||
});
|
||||
@ -385,7 +373,11 @@ export class TaskLoop {
|
||||
|
||||
} else {
|
||||
// 一些提示
|
||||
break;
|
||||
}
|
||||
|
||||
// 回答聚合完成后根据 stop 来决定是否提前中断
|
||||
if (doConverationResult.stop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,18 @@ export function makeUsageStatistic(extraInfo: IExtraInfo): UsageStatistic | unde
|
||||
total: usage.prompt_tokens + usage.completion_tokens,
|
||||
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;
|
||||
|
@ -25,7 +25,7 @@
|
||||
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
|
||||
<Message.Toolcall
|
||||
: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>
|
||||
@ -97,6 +97,7 @@ const renderMessages = computed(() => {
|
||||
messages.push({
|
||||
role: 'assistant/tool_calls',
|
||||
content: message.content,
|
||||
toolResults: Array(message.tool_calls.length).fill([]),
|
||||
tool_calls: message.tool_calls,
|
||||
showJson: ref(false),
|
||||
extraInfo: {
|
||||
@ -116,8 +117,16 @@ const renderMessages = computed(() => {
|
||||
// 如果是工具,则合并进入 之前 assistant 一起渲染
|
||||
const lastAssistantMessage = messages[messages.length - 1];
|
||||
if (lastAssistantMessage.role === 'assistant/tool_calls') {
|
||||
lastAssistantMessage.toolResult = message.content;
|
||||
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
||||
lastAssistantMessage.toolResults[message.index] = message.content;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -165,6 +174,11 @@ provide('streamingToolCalls', streamingToolCalls);
|
||||
provide('isLoading', isLoading);
|
||||
provide('autoScroll', autoScroll);
|
||||
|
||||
const chatContext = {
|
||||
handleSend: undefined
|
||||
};
|
||||
provide('chatContext', chatContext);
|
||||
|
||||
// 修改 scrollToBottom 方法
|
||||
async function scrollToBottom() {
|
||||
if (!scrollbarRef.value || !messageListRef.value) return;
|
||||
|
@ -10,33 +10,49 @@ function escapeHtml(unsafe: string) {
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// 导出默认的 highlight 函数
|
||||
export default function highlight(str: string, lang: string) {
|
||||
// 创建代码块容器
|
||||
let container = `<div class="openmcp-code-block">`;
|
||||
|
||||
// 添加复制按钮(右上角)
|
||||
container += `
|
||||
<div class="code-header">
|
||||
<div class="code-language">${lang || ''}</div>
|
||||
<button class="copy-button" onclick="copyCode(this)">复制</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (lang && Prism.languages[lang]) {
|
||||
// 使用 Prism 高亮代码
|
||||
const highlightedCode = Prism.highlight(str, Prism.languages[lang], lang);
|
||||
// 添加代码区域
|
||||
container += `<pre class="language-${lang}"><code class="language-${lang}">${highlightedCode}</code></pre>`;
|
||||
} else {
|
||||
// 普通代码块
|
||||
container += `<pre class="language-none"><code>${escapeHtml(str)}</code></pre>`;
|
||||
}
|
||||
|
||||
container += `</div>`;
|
||||
return container;
|
||||
interface HighlightOption {
|
||||
needTools?: boolean
|
||||
}
|
||||
|
||||
// 导出默认的 highlight 函数
|
||||
export default function highlight(option: HighlightOption = {}) {
|
||||
const {
|
||||
needTools = true
|
||||
} = option;
|
||||
|
||||
return (str: string, lang: string) => {
|
||||
|
||||
if (needTools) {
|
||||
// 创建代码块容器
|
||||
let container = `<div class="openmcp-code-block">`;
|
||||
|
||||
// 添加复制按钮(右上角)
|
||||
container += `
|
||||
<div class="code-header">
|
||||
<div class="code-language">${lang || ''}</div>
|
||||
<button class="copy-button" onclick="copyCode(this)">复制</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (lang && Prism.languages[lang]) {
|
||||
// 使用 Prism 高亮代码
|
||||
const highlightedCode = Prism.highlight(str, Prism.languages[lang], lang);
|
||||
// 添加代码区域
|
||||
container += `<pre class="language-${lang}"><code class="language-${lang}">${highlightedCode}</code></pre>`;
|
||||
} else {
|
||||
// 普通代码块
|
||||
container += `<pre class="language-none"><code>${escapeHtml(str)}</code></pre>`;
|
||||
}
|
||||
|
||||
container += `</div>`;
|
||||
return container;
|
||||
} else {
|
||||
return Prism.highlight(str, Prism.languages[lang], lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 全局复制函数
|
||||
(window as any).copyCode = function (button: HTMLElement) {
|
||||
const codeBlock = button.closest('.openmcp-code-block');
|
||||
@ -44,7 +60,13 @@ export default function highlight(str: string, lang: string) {
|
||||
const codeElement = codeBlock.querySelector('code');
|
||||
const code = codeElement?.textContent || '';
|
||||
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
// 支持 nodejs 下运行
|
||||
const thisWindow = window as any;
|
||||
if (!thisWindow || !thisWindow.navigator || !thisWindow.navigator.clipboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.navigator.clipboard.writeText(code).then(() => {
|
||||
const originalText = button.textContent;
|
||||
button.textContent = '已复制';
|
||||
setTimeout(() => {
|
||||
|
@ -3,7 +3,7 @@ import MarkdownKatex from './markdown-katex';
|
||||
import MarkdownHighlight from './markdown-highlight';
|
||||
|
||||
const md = new MarkdownIt({
|
||||
highlight: MarkdownHighlight,
|
||||
highlight: MarkdownHighlight({ needTools: true }),
|
||||
});
|
||||
|
||||
md.use(MarkdownKatex, {
|
||||
@ -18,6 +18,41 @@ export const markdownToHtml = (markdown: string) => {
|
||||
return md.render(markdown);
|
||||
};
|
||||
|
||||
const pureHighLightMd = new MarkdownIt({
|
||||
highlight: MarkdownHighlight({ needTools: false }),
|
||||
});
|
||||
|
||||
export const copyToClipboard = (text: string) => {
|
||||
// 支持 nodejs 下运行
|
||||
const thisWindow = window as any;
|
||||
if (!thisWindow || !thisWindow.navigator || !thisWindow.navigator.clipboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
return navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
const tryParseJson = (text: string) => {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const prettifyObj = (obj: object | string) => {
|
||||
const rawObj = typeof obj === 'string' ? tryParseJson(obj) : obj;
|
||||
return JSON.stringify(rawObj, null, 2);
|
||||
}
|
||||
|
||||
export const renderJson = (obj: object | string | undefined) => {
|
||||
if (!obj) {
|
||||
return '<span>Invalid JSON</span>';
|
||||
}
|
||||
|
||||
const jsonString = prettifyObj(obj);
|
||||
const md = "```json\n" + jsonString + "\n```";
|
||||
const html = pureHighLightMd.render(md);
|
||||
return html;
|
||||
}
|
@ -36,7 +36,6 @@
|
||||
max-height: inherit;
|
||||
height: inherit;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
@ -251,7 +250,6 @@
|
||||
max-height: inherit;
|
||||
height: inherit;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="message-role">Agent</div>
|
||||
<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>
|
||||
<MessageMeta :message="props.message" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
import { computed, defineProps } from 'vue';
|
||||
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
||||
|
||||
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>
|
||||
|
||||
<style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="message-avatar">
|
||||
<div class="message-avatar streaming-box">
|
||||
<span class="iconfont icon-chat"></span>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
@ -11,7 +11,7 @@
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
<div class="message-text streaming-box">
|
||||
<span v-html="waitingMarkdownToHtml(streamingContent)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-scrollbar width="100%">
|
||||
<el-scrollbar width="100%" max-height="300px">
|
||||
<div v-if="props.item.type === 'text'" class="tool-text">
|
||||
{{ props.item.text }}
|
||||
</div>
|
||||
|
@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<div class="message-role">
|
||||
<span class="message-reminder" v-if="!props.message.toolResult">
|
||||
<span class="message-reminder" v-if="callingTools">
|
||||
Agent 正在使用工具
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-text tool_calls" :class="[currentMessageLevel]">
|
||||
|
||||
<!-- 工具的消息 -->
|
||||
<div v-if="props.message.content" v-html="markdownToHtml(props.message.content)"></div>
|
||||
|
||||
<!-- 工具的调用 -->
|
||||
<el-collapse v-model="activeNames" v-if="props.message.tool_calls">
|
||||
<el-collapse-item name="tool">
|
||||
<template #title>
|
||||
@ -26,86 +29,115 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<div class="tool-arguments">
|
||||
<div class="inner">
|
||||
<div v-html="jsonResultToHtml(props.message.tool_calls[0].function.arguments)"></div>
|
||||
<div v-for="(toolResult, toolIndex) in props.message.toolResults" :key="toolIndex"
|
||||
class="toolcall-item">
|
||||
|
||||
<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 class="tool-arguments">
|
||||
<json-render :json="props.message.tool_calls[toolIndex].function.arguments"/>
|
||||
</div>
|
||||
|
||||
<!-- 工具调用结果 -->
|
||||
<div v-if="props.message.toolResult">
|
||||
<div v-if="toolResult.length > 0">
|
||||
<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>
|
||||
{{ isValid ? '响应': '错误' }}
|
||||
<el-button v-if="!isValid" size="small"
|
||||
@click="gotoIssue()"
|
||||
>
|
||||
反馈
|
||||
{{ isValid(toolResult) ? t("response") : t('error') }}
|
||||
<el-button size="small" @click="gotoIssue()">
|
||||
{{ t('feedback') }}
|
||||
</el-button>
|
||||
</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"
|
||||
inactive-text="Text" style="margin-left: 10px; width: 200px;"
|
||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="tool-result" v-if="isValid">
|
||||
<div class="tool-result" v-if="isValid(toolResult)">
|
||||
<!-- 展示 JSON -->
|
||||
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
||||
<div class="inner">
|
||||
<div v-html="toHtml(props.message.toolResult)"></div>
|
||||
</div>
|
||||
<json-render :json="props.message.toolResults[toolIndex]"/>
|
||||
</div>
|
||||
|
||||
<!-- 展示富文本 -->
|
||||
<span v-else>
|
||||
<div v-for="(item, index) in props.message.toolResult" :key="index"
|
||||
class="response-item"
|
||||
>
|
||||
<ToolcallResultItem
|
||||
:item="item"
|
||||
@update:item="value => updateToolCallResultItem(value, index)"
|
||||
@update:ocr-done="value => collposePanel()"
|
||||
/>
|
||||
<div v-for="(item, index) in props.message.toolResults[toolIndex]" :key="index"
|
||||
class="response-item">
|
||||
<ToolcallResultItem :item="item"
|
||||
@update:item="value => updateToolCallResultItem(value, toolIndex, index)"
|
||||
@update:ocr-done="value => collposePanel()" />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="tool-result">
|
||||
<div class="tool-result-content"
|
||||
v-for="(error, index) of collectErrors"
|
||||
:key="index"
|
||||
>
|
||||
<div class="tool-result-content" v-for="(error, index) of collectErrors(toolResult)"
|
||||
:key="index">
|
||||
{{ error }}
|
||||
</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 v-if="toolIndex === props.message.toolResults.length - 1" :message="message" />
|
||||
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import MessageMeta from './message-meta.vue';
|
||||
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
||||
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 ToolcallResultItem from './toolcall-result-item.vue';
|
||||
import JsonRender from '@/components/json-render/index.vue';
|
||||
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: Object as PropType<IRenderMessage>,
|
||||
type: Object as PropType<IToolRenderMessage>,
|
||||
required: true
|
||||
},
|
||||
tabId: {
|
||||
@ -115,20 +147,37 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const hasOcr = computed(() => {
|
||||
for (const item of props.message.toolResult || []) {
|
||||
const metaInfo = item._meta || {};
|
||||
const { ocr = false } = metaInfo;
|
||||
if (ocr) {
|
||||
return true;
|
||||
if (props.message.role === 'assistant/tool_calls') {
|
||||
for (const toolResult of props.message.toolResults) {
|
||||
for (const item of toolResult) {
|
||||
const metaInfo = item._meta || {};
|
||||
const { ocr = false } = metaInfo;
|
||||
if (ocr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
() => props.message.toolResult,
|
||||
() => props.message,
|
||||
(value, _) => {
|
||||
if (hasOcr.value) {
|
||||
return;
|
||||
@ -146,34 +195,15 @@ function collposePanel() {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将工具调用结果转换成 html
|
||||
* @param toolResult
|
||||
*/
|
||||
const toHtml = (toolResult: ToolCallContent[]) => {
|
||||
const formattedJson = JSON.stringify(toolResult, null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
return html;
|
||||
};
|
||||
|
||||
const jsonResultToHtml = (jsonResult: string) => {
|
||||
try {
|
||||
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(() => {
|
||||
|
||||
function isValid(toolResult: ToolCallContent[]) {
|
||||
try {
|
||||
const item = props.message.toolResult![0];
|
||||
const item = toolResult[0];
|
||||
if (item.type === 'error') {
|
||||
return false;
|
||||
}
|
||||
@ -181,22 +211,34 @@ const isValid = computed(() => {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const currentMessageLevel = computed(() => {
|
||||
if (!isValid.value) {
|
||||
return 'error';
|
||||
|
||||
// 此时正在等待 mcp server 给出回应
|
||||
for (const toolResult of props.message.toolResults) {
|
||||
if (toolResult.length === 0) {
|
||||
return 'info';
|
||||
}
|
||||
|
||||
if (!isValid(toolResult)) {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
if (props.message.extraInfo.state != MessageState.Success) {
|
||||
|
||||
if (props.message.extraInfo.state !== MessageState.Success) {
|
||||
return 'warning';
|
||||
}
|
||||
return 'info';
|
||||
})
|
||||
|
||||
const collectErrors = computed(() => {
|
||||
return 'info';
|
||||
});
|
||||
|
||||
|
||||
function collectErrors(toolResult: ToolCallContent[]) {
|
||||
const errorMessages = [];
|
||||
try {
|
||||
const errorResults = props.message.toolResult!.filter(item => item.type === 'error');
|
||||
const errorResults = toolResult.filter(item => item.type === 'error');
|
||||
console.log(errorResults);
|
||||
|
||||
for (const errorResult of errorResults) {
|
||||
@ -206,12 +248,12 @@ const collectErrors = computed(() => {
|
||||
} catch {
|
||||
return errorMessages;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:tool-result']);
|
||||
const emits = defineEmits(['update:tool-result']);
|
||||
|
||||
function updateToolCallResultItem(value: any, index: number) {
|
||||
emit('update:tool-result', value, index);
|
||||
function updateToolCallResultItem(value: any, toolIndex: number, index: number) {
|
||||
emits('update:tool-result', value, toolIndex, index);
|
||||
}
|
||||
|
||||
</script>
|
||||
@ -223,6 +265,13 @@ function updateToolCallResultItem(value: any, index: number) {
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border: 1px solid var(--el-color-warning);
|
||||
}
|
||||
@ -232,7 +281,7 @@ function updateToolCallResultItem(value: any, index: number) {
|
||||
}
|
||||
|
||||
.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 {
|
||||
@ -244,7 +293,7 @@ function updateToolCallResultItem(value: any, index: number) {
|
||||
}
|
||||
|
||||
.message-text.tool_calls.error .tool-result {
|
||||
background-color: rgba(245, 108, 108, 0.5);
|
||||
background-color: rgba(245, 108, 108, 0.5);
|
||||
}
|
||||
|
||||
|
||||
@ -258,6 +307,9 @@ function updateToolCallResultItem(value: any, index: number) {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.toolcall-item .tool-calls {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.tool-call-item {
|
||||
margin-bottom: 10px;
|
||||
|
@ -34,7 +34,7 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, PropType, inject } from 'vue';
|
||||
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 { ElMessage } from 'element-plus';
|
||||
@ -58,7 +58,7 @@ const tabStorage = tab.storage as ChatStorage;
|
||||
const isEditing = ref(false);
|
||||
const userInput = ref('');
|
||||
|
||||
const handleSend = inject<(newMessage: string | undefined) => void>('handleSend');
|
||||
const chatContext = inject('chatContext') as any;
|
||||
|
||||
const toggleEdit = () => {
|
||||
isEditing.value = !isEditing.value;
|
||||
@ -70,10 +70,12 @@ const toggleEdit = () => {
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
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)
|
||||
tabStorage.messages.splice(index);
|
||||
handleSend(userInput.value);
|
||||
chatContext.handleSend(userInput.value);
|
||||
|
||||
isEditing.value = false;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formattedJson }}
|
||||
<json-render :json="tabStorage.lastPromptGetResponse"/>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@ -36,6 +36,7 @@ import { defineComponent, defineProps, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { tabs } from '../panel';
|
||||
import { PromptStorage } from './prompts';
|
||||
import JsonRender from '@/components/json-render/index.vue';
|
||||
|
||||
defineComponent({ name: 'prompt-logger' });
|
||||
const { t } = useI18n();
|
||||
@ -52,13 +53,6 @@ const tabStorage = tab.storage as PromptStorage;
|
||||
|
||||
const showRawJson = ref(false);
|
||||
|
||||
const formattedJson = computed(() => {
|
||||
try {
|
||||
return JSON.stringify(tabStorage.lastPromptGetResponse, null, 2);
|
||||
} catch {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<el-switch v-else-if="param.type === 'boolean'" v-model="tabStorage.formData[param.name]" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-form-item v-if="tabStorage.currentType === 'template'">
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
||||
{{ t('read-resource') }}
|
||||
</el-button>
|
||||
@ -24,6 +24,11 @@
|
||||
{{ t('reset') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item v-else>
|
||||
<el-button @click="handleSubmit">
|
||||
{{ t("refresh") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
@ -142,9 +147,7 @@ function getUri() {
|
||||
}
|
||||
|
||||
const currentResourceName = props.tabId >= 0 ? tabStorage.currentResourceName : props.currentResourceName;
|
||||
|
||||
const targetResource = resourcesManager.resources.find(resources => resources.name === currentResourceName);
|
||||
|
||||
return targetResource?.uri;
|
||||
}
|
||||
|
||||
@ -154,9 +157,7 @@ async function handleSubmit() {
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: uri });
|
||||
|
||||
tabStorage.lastResourceReadResponse = msg;
|
||||
|
||||
emits('resource-get-response', msg);
|
||||
}
|
||||
|
||||
|
@ -75,12 +75,18 @@ function reloadResources(option: { first: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(resource: Resources) {
|
||||
async function handleClick(resource: Resources) {
|
||||
tabStorage.currentType = 'resource';
|
||||
tabStorage.currentResourceName = resource.name;
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
|
||||
emits('resource-selected', resource);
|
||||
|
||||
// 更新资源
|
||||
if (props.tabId >= 0) {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: resource.uri });
|
||||
tabStorage.lastResourceReadResponse = msg;
|
||||
}
|
||||
}
|
||||
|
||||
let commandCancel: (() => void);
|
||||
|
@ -37,7 +37,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formattedJson }}
|
||||
<json-render :json="tabStorage.lastResourceReadResponse"/>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@ -50,6 +50,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { tabs } from '../panel';
|
||||
import { ResourceStorage } from './resources';
|
||||
import { getImageBlobUrlByBase64 } from '@/hook/util';
|
||||
import JsonRender from '@/components/json-render/index.vue';
|
||||
|
||||
defineComponent({ name: 'resource-logger' });
|
||||
const { t } = useI18n();
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
<!-- 展示 json -->
|
||||
<template v-else>
|
||||
{{ formattedJson }}
|
||||
<json-render :json="tabStorage.lastToolCallResponse"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@ -40,7 +40,7 @@ import { defineComponent, defineProps, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { tabs } from '../panel';
|
||||
import { ToolStorage } from './tools';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import JsonRender from '@/components/json-render/index.vue';
|
||||
|
||||
defineComponent({ name: 'tool-logger' });
|
||||
const { t } = useI18n();
|
||||
@ -57,17 +57,6 @@ const tabStorage = tab.storage as ToolStorage;
|
||||
|
||||
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';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -30,9 +30,6 @@ export function callTool(toolName: string, toolArgs: Record<string, any>) {
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
pinkLog('callTool');
|
||||
console.log(toolArgs);
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'tools/call',
|
||||
data: {
|
||||
|
@ -147,8 +147,8 @@ interface GetColorOption {
|
||||
|
||||
export class MacroColor {
|
||||
private option: ComputedColorOption;
|
||||
private rootStyles: CSSStyleDeclaration;
|
||||
private theme: 'light' | 'dark';
|
||||
private rootStyles?: CSSStyleDeclaration;
|
||||
private theme: 'light' | 'dark' = 'dark';
|
||||
public foregroundColor: RgbColor | undefined;
|
||||
public backgroundColor: RgbColor | undefined;
|
||||
public foregroundColorString: string;
|
||||
@ -195,7 +195,7 @@ export class MacroColor {
|
||||
|
||||
if (mode === 'svg') {
|
||||
// svg 模式下,导出的效果和 webview 渲染效果基本一致,直接导出即可
|
||||
return rootStyles.getPropertyValue(macroName);
|
||||
return rootStyles?.getPropertyValue(macroName) || '#fff';
|
||||
}
|
||||
|
||||
// pdf 模式需要对黑色主题的几个特殊颜色进行处理,并对所有透明颜色进行混合处理
|
||||
@ -208,7 +208,7 @@ export class MacroColor {
|
||||
}
|
||||
}
|
||||
|
||||
const colorString = rootStyles.getPropertyValue(macroName);
|
||||
const colorString = rootStyles?.getPropertyValue(macroName) || '#fff';
|
||||
if (!colorString) {
|
||||
// 如果 macroName 不存在,返回空字符串
|
||||
return colorString;
|
||||
|
@ -18,6 +18,12 @@ export function getThemeColor(): 'light' | 'dark' {
|
||||
if (themeColor) {
|
||||
return themeColor;
|
||||
}
|
||||
|
||||
const myDocument = document as any;
|
||||
if (!myDocument) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const backgroundColorString = rootStyles.getPropertyValue('--background');
|
||||
const backgroundColor = Color.parseColor(backgroundColorString);
|
||||
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "اختر الإعداد المسبق",
|
||||
"cwd": "دليل التنفيذ",
|
||||
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP",
|
||||
"return": "عودة"
|
||||
"return": "عودة",
|
||||
"error": "خطأ",
|
||||
"feedback": "تعليقات",
|
||||
"waiting-mcp-server": "في انتظار استجابة خادم MCP"
|
||||
}
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "Voreinstellung auswählen",
|
||||
"cwd": "Ausführungsverzeichnis",
|
||||
"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",
|
||||
"cwd": "Execution directory",
|
||||
"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",
|
||||
"cwd": "Répertoire d'exécution",
|
||||
"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": "プリセットを選択",
|
||||
"cwd": "実行ディレクトリ",
|
||||
"mcp-server-timeout": "MCPツールの最大呼び出し時間",
|
||||
"return": "戻る"
|
||||
"return": "戻る",
|
||||
"error": "エラー",
|
||||
"feedback": "フィードバック",
|
||||
"waiting-mcp-server": "MCPサーバーの応答を待機中"
|
||||
}
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "프리셋 선택",
|
||||
"cwd": "실행 디렉터리",
|
||||
"mcp-server-timeout": "MCP 도구 최대 호출 시간",
|
||||
"return": "돌아가기"
|
||||
"return": "돌아가기",
|
||||
"error": "오류",
|
||||
"feedback": "피드백",
|
||||
"waiting-mcp-server": "MCP 서버 응답 대기 중"
|
||||
}
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "Выбрать预设",
|
||||
"cwd": "Каталог выполнения",
|
||||
"mcp-server-timeout": "Максимальное время вызова инструмента MCP",
|
||||
"return": "Назад"
|
||||
"return": "Назад",
|
||||
"error": "Ошибка",
|
||||
"feedback": "Обратная связь",
|
||||
"waiting-mcp-server": "Ожидание ответа от сервера MCP"
|
||||
}
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "选择预设",
|
||||
"cwd": "执行目录",
|
||||
"mcp-server-timeout": "MCP工具最长调用时间",
|
||||
"return": "返回"
|
||||
"return": "返回",
|
||||
"error": "错误",
|
||||
"feedback": "反馈",
|
||||
"waiting-mcp-server": "等待 MCP 服务器响应"
|
||||
}
|
@ -152,5 +152,8 @@
|
||||
"choose-presetting": "選擇預設",
|
||||
"cwd": "執行目錄",
|
||||
"mcp-server-timeout": "MCP工具最長調用時間",
|
||||
"return": "返回"
|
||||
"return": "返回",
|
||||
"error": "錯誤",
|
||||
"feedback": "反饋",
|
||||
"waiting-mcp-server": "等待MCP伺服器響應"
|
||||
}
|
@ -39,7 +39,9 @@ const router = createRouter({
|
||||
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.title) {
|
||||
const myDocument = document as any;
|
||||
|
||||
if (to.meta.title && myDocument) {
|
||||
document.title = `OpenMCP | ${to.meta.title}`;
|
||||
}
|
||||
next();
|
||||
|
@ -6,7 +6,7 @@
|
||||
</span>
|
||||
|
||||
<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>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { pinkLog } from '../setting/util';
|
||||
import { arrowMiddleware, ElMessage } from 'element-plus';
|
||||
import { ILaunchSigature } from '@/hook/type';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { OpenMcpSupportPlatform } from '@/api/platform';
|
||||
|
||||
export const connectionMethods = reactive({
|
||||
@ -172,10 +171,7 @@ async function launchStdio(namespace: string) {
|
||||
message: msg
|
||||
});
|
||||
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: msg
|
||||
});
|
||||
ElMessage.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,10 +222,7 @@ async function launchSSE(namespace: string) {
|
||||
message: msg
|
||||
});
|
||||
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: msg
|
||||
});
|
||||
ElMessage.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 { llmManager } from './llm';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { makeUsageStatistic } from '@/components/main-panel/chat/core/usage';
|
||||
|
||||
export const llmSettingRef = ref<any>(null);
|
||||
|
||||
export const simpleTestResult = reactive<{
|
||||
done: boolean,
|
||||
start: boolean,
|
||||
error: any
|
||||
error: any,
|
||||
tps: string | number | undefined
|
||||
}>({
|
||||
done: false,
|
||||
start: false,
|
||||
error: '',
|
||||
tps: undefined
|
||||
});
|
||||
|
||||
export function makeSimpleTalk() {
|
||||
export async function makeSimpleTalk() {
|
||||
simpleTestResult.done = false;
|
||||
simpleTestResult.start = true;
|
||||
simpleTestResult.tps = undefined;
|
||||
|
||||
// 使用最简单的 hello 来测试
|
||||
const testMessage = 'hello';
|
||||
@ -38,18 +42,35 @@ export function makeSimpleTalk() {
|
||||
}
|
||||
};
|
||||
|
||||
loop.setMaxEpochs(1);
|
||||
|
||||
loop.registerOnDone(() => {
|
||||
console.log('done');
|
||||
simpleTestResult.error = '';
|
||||
simpleTestResult.done = true;
|
||||
simpleTestResult.start = false;
|
||||
});
|
||||
|
||||
loop.registerOnError(error => {
|
||||
console.log(error);
|
||||
simpleTestResult.error = error;
|
||||
const errorReason = error.msg;
|
||||
const errorText = JSON.stringify(errorReason);
|
||||
|
||||
simpleTestResult.error = errorText;
|
||||
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>
|
||||
|
||||
<!-- 根据不同模型展示不同的接入点 -->
|
||||
<!-- TODO: 根据不同模型展示不同的接入点 -->
|
||||
<div v-if="false">
|
||||
|
||||
</div>
|
||||
@ -80,7 +80,7 @@
|
||||
<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>
|
||||
|
||||
@ -286,6 +286,17 @@ function handleCommand(command: {type: string, index: number}) {
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ export function onGeneralColorChange(colorString: string) {
|
||||
}
|
||||
const { r, g, b } = color;
|
||||
|
||||
const myDocument = document as any;
|
||||
if (!myDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
'--main-color', `rgb(${r}, ${g}, ${b})`);
|
||||
|
||||
|
@ -2,22 +2,25 @@
|
||||
<div class="connect-test" v-if="simpleTestResult.done || simpleTestResult.error">
|
||||
<div class="test-result">
|
||||
<div class="result-item" v-if="simpleTestResult.done">
|
||||
<span class="iconfont icon-success"></span>
|
||||
<span>{{ "✅ okey dockey :D" }}</span>
|
||||
<span class="iconfont icon-dui"></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 class="result-item error" v-if="simpleTestResult.error">
|
||||
<span class="iconfont icon-error"></span>
|
||||
<span>{{ '❌ ' + simpleTestResult.error }}</span>
|
||||
<span class="iconfont icon-cuo"></span>
|
||||
<span>{{ ' ' + simpleTestResult.error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { simpleTestResult } from './api';
|
||||
|
||||
defineComponent({ name: 'connect-test' });
|
||||
const { t } = useI18n();
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -43,6 +46,14 @@ defineComponent({ name: 'connect-test' });
|
||||
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 {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
@ -13,7 +13,20 @@ export class LlmController {
|
||||
};
|
||||
}
|
||||
|
||||
await streamingChatCompletion(data, webview);
|
||||
|
||||
try {
|
||||
await streamingChatCompletion(data, webview);
|
||||
} catch (error) {
|
||||
console.log('error' + error);
|
||||
|
||||
webview.postMessage({
|
||||
command: 'llm/chat/completions/error',
|
||||
data: {
|
||||
msg: error
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
code: -1,
|
||||
|
@ -58,9 +58,6 @@ export async function streamingChatCompletion(
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(chunk);
|
||||
|
||||
|
||||
if (chunk.choices) {
|
||||
const chunkResult = {
|
||||
code: 200,
|
||||
|
@ -31,6 +31,7 @@ export interface McpOptions {
|
||||
// SSE 特定选项
|
||||
url?: string;
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
// 通用客户端选项
|
||||
clientName?: string;
|
||||
clientVersion?: string;
|
||||
|
@ -42,7 +42,8 @@ export class McpClient {
|
||||
command: this.options.command || '',
|
||||
args: this.options.args || [],
|
||||
cwd: this.options.cwd || process.cwd(),
|
||||
stderr: 'pipe'
|
||||
stderr: 'pipe',
|
||||
env: this.options.env,
|
||||
});
|
||||
|
||||
break;
|
||||
@ -119,8 +120,8 @@ export class McpClient {
|
||||
|
||||
// 调用工具
|
||||
public async callTool(options: { name: string; arguments: Record<string, any>, callToolOption?: any }) {
|
||||
|
||||
const { callToolOption, ...methodArgs } = options;
|
||||
console.log('callToolOption', callToolOption);
|
||||
return await this.client.callTool(methodArgs, undefined, callToolOption);
|
||||
}
|
||||
}
|
||||
|
61
webpack/webpack.task-loop.js
Normal file
61
webpack/webpack.task-loop.js
Normal file
@ -0,0 +1,61 @@
|
||||
const path = require('path');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development', // 设置为 development 模式
|
||||
devtool: 'source-map', // 生成 source map 以便调试
|
||||
entry: './renderer/src/components/main-panel/chat/core/task-loop.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../openmcp-sdk'),
|
||||
filename: 'task-loop.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
target: 'node',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '../renderer/src'), // 修正路径别名
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: {
|
||||
loader: 'null-loader'
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimize: false, // 禁用代码压缩
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false, // 禁用提取许可证文件
|
||||
}),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
window: {
|
||||
nodejs: true,
|
||||
navigator: {
|
||||
userAgent: 2
|
||||
},
|
||||
performance: {
|
||||
now: () => Date.now()
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
vue: 'vue', // 不打包 vue 库
|
||||
'element-plus': './tool.js'
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user