支持 resources/list 的调度

This commit is contained in:
锦恢 2025-05-06 20:45:47 +08:00
parent c716cbc6ca
commit 8d549d17e2
39 changed files with 452 additions and 115 deletions

View File

@ -1,5 +1,9 @@
# Change Log
## [main] 0.0.7
- 优化页面布局,使得调试内容更加紧凑
- 扩大默认的上下文长度
- 增加「通用选项」用于设置mcp服务器的最大的等待时间
## [main] 0.0.6
- 修复部分因为服务器名称特殊字符而导致的保存实效的错误

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4870215 */
src: url('iconfont.woff2?t=1745774700883') format('woff2'),
url('iconfont.woff?t=1745774700883') format('woff'),
url('iconfont.ttf?t=1745774700883') format('truetype');
src: url('iconfont.woff2?t=1746529081655') format('woff2'),
url('iconfont.woff?t=1746529081655') format('woff'),
url('iconfont.ttf?t=1746529081655') format('truetype');
}
.iconfont {
@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-timeout:before {
content: "\edf5";
}
.icon-dui:before {
content: "\e627";
}
.icon-cuo:before {
content: "\ed1a";
}
.icon-video:before {
content: "\e865";
}

Binary file not shown.

View File

@ -94,7 +94,7 @@ onMounted(async () => {
<style>
.main {
height: calc(100vh - 50px);
height: calc(100vh - 5px);
display: flex;
justify-content: center;
}

View File

@ -38,7 +38,7 @@ if (!tabStorage.settings) {
enableTools: [],
enableWebSearch: false,
temperature: 0.7,
contextLength: 10,
contextLength: 20,
systemPrompt: ''
} as ChatSetting;
}

View File

@ -70,20 +70,19 @@ function setActiveTab(index: number) {
<style>
.main-panel-container {
display: flex;
justify-content: center;
flex-direction: column;
width: 100%;
min-width: 800px;
height: 100%;
margin-left: 20px;
margin-left: 5px;
}
.main-panel {
background-color: var(--sidebar);
border-radius: 1.2em;
width: 100%;
height: 90%;
height: calc(100% - 35px);
}
.scroll-tabs-container {
@ -92,13 +91,13 @@ function setActiveTab(index: number) {
}
.tabs-container {
height: 78px;
height: 30px;
width: 90%;
background-color: var(--background);
display: flex;
align-items: center;
user-select: none;
padding: 0 10px;
margin-bottom: 5px;
}
.tabs-container .el-scrollbar {
@ -107,12 +106,12 @@ function setActiveTab(index: number) {
.tabs-container .tab {
white-space: nowrap;
margin: 5px;
font-size: 13px;
margin-right: 5px;
font-size: 12px;
width: 120px;
border-radius: .5em;
background-color: var(--sidebar);
padding: 10px;
padding: 3px 10px;
display: flex;
align-items: center;
transition: var(--animation-3s);
@ -160,11 +159,9 @@ function setActiveTab(index: number) {
.tabs-container .add-button {
cursor: pointer;
font-size: 20px;
font-size: 15px;
margin-left: 5px;
border-radius: .5em;
height: 35px;
width: 35px;
display: flex;
align-items: center;
justify-content: center;

View File

@ -6,11 +6,14 @@
<span class="iconfont icon-file"></span>
{{ t("resources") + t("module") }}
</h2>
<h3><code>resources/templates/list</code></h3>
<ResourceTemplates
<ResourceListTemplates
:tab-id="props.tabId"
></ResourceTemplates>
></ResourceListTemplates>
<ResourceList
:tab-id="props.tabId"
></ResourceList>
</div>
<div class="right">
@ -29,7 +32,10 @@
<script setup lang="ts">
import { defineProps } from 'vue';
import { useI18n } from 'vue-i18n';
import ResourceTemplates from './resource-templates.vue';
import ResourceListTemplates from './resource-list-templates.vue';
import ResourceList from './resource-list.vue';
import ResourceReader from './resouce-reader.vue';
import ResourceLogger from './resource-logger.vue';
@ -42,8 +48,6 @@ const props = defineProps({
}
});
</script>
<style scoped>

View File

@ -122,21 +122,28 @@ const resetForm = () => {
responseData.value = undefined;
}
function getUri() {
if (tabStorage.currentType === 'template') {
const fillFn = currentResource.value.fill;
const uri = fillFn(tabStorage.formData);
return uri;
}
const targetResource = resourcesManager.resources.find(resources => resources.name === tabStorage.currentResourceName);
return targetResource?.uri;
}
//
function handleSubmit() {
const fillFn = currentResource.value.fill;
const uri = fillFn(tabStorage.formData);
async function handleSubmit() {
const uri = getUri();
console.log(uri);
const bridge = useMessageBridge();
bridge.addCommandListener('resources/read', (data: CasualRestAPI<ResourcesReadResponse>) => {
tabStorage.lastResourceReadResponse = data.msg;
}, { once: true });
bridge.postMessage({
command: 'resources/read',
data: { resourceUri: uri }
});
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: uri });
if (code === 200) {
tabStorage.lastResourceReadResponse = msg;
}
}
//

View File

@ -1,10 +1,18 @@
<template>
<h3 class="resource-template">
<code>resources/templates/list</code>
<span
class="iconfont icon-restart"
@click="reloadResources({ first: false })"
></span>
</h3>
<div class="resource-template-container-scrollbar">
<el-scrollbar height="500px">
<el-scrollbar height="500px" v-if="resourcesManager.templates.length > 0">
<div class="resource-template-container">
<div
class="item"
:class="{ 'active': tabStorage.currentResourceName === template.name }"
:class="{ 'active': tabStorage.currentType === 'template' && tabStorage.currentResourceName === template.name }"
v-for="template of resourcesManager.templates"
:key="template.name"
@click="handleClick(template)"
@ -14,21 +22,16 @@
</div>
</div>
</el-scrollbar>
<div v-else style="padding: 10px;">
empty
</div>
</div>
<div class="resource-template-function-container">
<el-button
type="primary"
@click="reloadResources({ first: false })"
>
{{ t('refresh') }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps } from 'vue';
import { onMounted, onUnmounted, defineProps, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { resourcesManager, ResourceStorage } from './resources';
import { tabs } from '../panel';
@ -63,6 +66,7 @@ function reloadResources(option: { first: boolean }) {
}
function handleClick(template: ResourceTemplate) {
tabStorage.currentType = 'template';
tabStorage.currentResourceName = template.name;
tabStorage.lastResourceReadResponse = undefined;
}
@ -73,11 +77,13 @@ onMounted(() => {
commandCancel = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
resourcesManager.templates = data.msg.resourceTemplates || [];
const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
if (targetResource === undefined) {
tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
tabStorage.lastResourceReadResponse = undefined;
}
if (tabStorage.currentType === 'template') {
const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
if (targetResource === undefined) {
tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
tabStorage.lastResourceReadResponse = undefined;
}
}
}, { once: false });
reloadResources({ first: true });
@ -88,10 +94,23 @@ onUnmounted(() => {
commandCancel();
}
})
</script>
<style>
h3.resource-template {
display: flex;
align-items: center;
}
h3.resource-template .iconfont.icon-restart {
margin-left: 10px;
cursor: pointer;
}
h3.resource-template .iconfont.icon-restart:hover {
color: var(--main-color);
}
.resource-template-container-scrollbar {
background-color: var(--background);
margin-bottom: 10px;

View File

@ -0,0 +1,173 @@
<template>
<h3 class="resource-template">
<code>resources/list</code>
<span
class="iconfont icon-restart"
@click="reloadResources({ first: false })"
></span>
</h3>
<div class="resource-template-container-scrollbar">
<el-scrollbar height="500px">
<div class="resource-template-container">
<div
class="item"
:class="{ 'active': tabStorage.currentType === 'resource' && tabStorage.currentResourceName === resource.name }"
v-for="resource of resourcesManager.resources"
:key="resource.uri"
@click="handleClick(resource)"
>
<span>{{ resource.name }}</span>
<span>{{ resource.mimeType }}</span>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, Resources, ResourcesListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { resourcesManager, ResourceStorage } from './resources';
import { tabs } from '../panel';
import { ElMessage } from 'element-plus';
const bridge = useMessageBridge();
const { t } = useI18n();
const props = defineProps({
tabId: {
type: Number,
required: true
}
});
const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ResourceStorage;
function reloadResources(option: { first: boolean }) {
bridge.postMessage({
command: 'resources/list'
});
if (!option.first) {
ElMessage({
message: t('finish-refresh'),
type: 'success',
duration: 3000,
showClose: true,
});
}
}
function handleClick(resource: Resources) {
tabStorage.currentType = 'resource';
tabStorage.currentResourceName = resource.name;
tabStorage.lastResourceReadResponse = undefined;
}
let commandCancel: (() => void);
onMounted(() => {
commandCancel = bridge.addCommandListener('resources/list', (data: CasualRestAPI<ResourcesListResponse>) => {
resourcesManager.resources = data.msg.resources || [];
if (tabStorage.currentType === 'resource') {
const targetResource = resourcesManager.resources.find(resources => resources.name === tabStorage.currentResourceName);
if (targetResource === undefined) {
tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
tabStorage.lastResourceReadResponse = undefined;
}
}
}, { once: false });
reloadResources({ first: true });
});
onUnmounted(() => {
if (commandCancel){
commandCancel();
}
})
</script>
<style>
h3.resource-template {
display: flex;
align-items: center;
}
h3.resource-template .iconfont.icon-restart {
margin-left: 10px;
cursor: pointer;
}
h3.resource-template .iconfont.icon-restart:hover {
color: var(--main-color);
}
.resource-template-container-scrollbar {
background-color: var(--background);
margin-bottom: 10px;
border-radius: .5em;
}
.resource-template-container {
height: fit-content;
display: flex;
flex-direction: column;
padding: 10px;
}
.resource-template-function-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.resource-template-function-container button {
width: 175px;
}
.resource-template-container > .item {
margin: 3px;
padding: 5px 10px;
border-radius: .3em;
user-select: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: var(--animation-3s);
}
.resource-template-container > .item:hover {
background-color: var(--main-light-color);
transition: var(--animation-3s);
}
.resource-template-container > .item.active {
background-color: var(--main-light-color);
transition: var(--animation-3s);
}
.resource-template-container > .item > span:first-child {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.resource-template-container > .item > span:last-child {
opacity: 0.6;
font-size: 12.5px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -20,7 +20,20 @@
>
<template v-if="!showRawJson">
<span v-for="(content, index) of tabStorage.lastResourceReadResponse?.contents || []" :key="index">
{{ content.text }}
<span v-if="content.mimeType === 'image/png'">
<img
class="resource-list-image"
:src="getImageBlobUrlByBase64(content.blob || '', content.mimeType)"
:alt="content.text"
style="max-width: 100%; max-height: 300px;"
/>
</span>
<span v-if="content.mimeType === 'image/jpeg'">
</span>
<span v-else>
{{ content.text }}
</span>
</span>
</template>
<template v-else>
@ -36,6 +49,7 @@ import { defineComponent, defineProps, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { tabs } from '../panel';
import { ResourceStorage } from './resources';
import { getImageBlobUrlByBase64 } from '@/hook/util';
defineComponent({ name: 'resource-logger' });
const { t } = useI18n();
@ -101,4 +115,8 @@ const formattedJson = computed(() => {
line-height: 1.5;
background-color: var(--sidebar);
}
.resource-list-image {
cursor: unset;
}
</style>

View File

@ -1,16 +1,19 @@
import { ResourcesReadResponse, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
import { ResourcesReadResponse, ResourceTemplate, Resources } from '@/hook/type';
import { reactive } from 'vue';
export const resourcesManager = reactive<{
current: ResourceTemplate | undefined
templates: ResourceTemplate[]
templates: ResourceTemplate[],
resources: Resources[]
}>({
current: undefined,
templates: []
templates: [],
resources: []
});
export interface ResourceStorage {
currentType: 'resource' | 'template';
currentResourceName: string;
lastResourceReadResponse?: ResourcesReadResponse;
formData: Record<string, any>;

View File

@ -142,9 +142,15 @@ const resetForm = () => {
};
async function handleExecute() {
if (!currentTool.value) return;
const toolResponse = await callTool(tabStorage.currentToolName, tabStorage.formData);
tabStorage.lastToolCallResponse = toolResponse;
if (!currentTool.value) return;
loading.value = true;
try {
tabStorage.lastToolCallResponse = undefined;
const toolResponse = await callTool(tabStorage.currentToolName, tabStorage.formData);
tabStorage.lastToolCallResponse = toolResponse;
} finally {
loading.value = false;
}
}
watch(() => tabStorage.currentToolName, () => {

View File

@ -15,6 +15,11 @@
</div>
</el-scrollbar>
</div>
<div>
<!-- resources/list -->
</div>
<div class="tool-list-function-container">
<el-button
type="primary"

View File

@ -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 { markdownToHtml } from '../chat/markdown/markdown';
import { useMessageBridge } from '@/api/message-bridge';
defineComponent({ name: 'tool-logger' });
const { t } = useI18n();

View File

@ -1,4 +1,5 @@
import { useMessageBridge } from '@/api/message-bridge';
import { mcpSetting } from '@/hook/mcp';
import { ToolsListResponse, ToolCallResponse, CasualRestAPI } from '@/hook/type';
import { pinkLog } from '@/views/setting/util';
import { reactive } from 'vue';
@ -36,7 +37,10 @@ export function callTool(toolName: string, toolArgs: Record<string, any>) {
command: 'tools/call',
data: {
toolName,
toolArgs: JSON.parse(JSON.stringify(toolArgs))
toolArgs: JSON.parse(JSON.stringify(toolArgs)),
callToolOption: {
timeout: mcpSetting.timeout * 1000
}
}
});
});

View File

@ -2,6 +2,7 @@
<div class="connected-status-container"
id="connected-status-container"
@click.stop="toggleConnectionPanel()"
:class="{ 'connected': connectionResult.success }"
>
<span class="mcp-server-info">
<el-tooltip
@ -14,12 +15,14 @@
</el-tooltip>
</span>
<span class="connect-status">
<span
class="status-circle"
:class="statusColorStyle"
>
<span v-if="connectionResult.success">
<span class="iconfont icon-connect"></span>
<span class="iconfont icon-dui"></span>
</span>
<span v-else>
<span class="iconfont icon-connect"></span>
<span class="iconfont icon-cuo"></span>
</span>
<span class="status-string">{{ statusString }}</span>
</span>
</div>
@ -35,22 +38,6 @@ defineComponent({ name: 'connected' });
const { t } = useI18n();
const statusString = computed(() => {
if (connectionResult.success) {
return t('connected');
} else {
return t('disconnected');
}
});
const statusColorStyle = computed(() => {
if (connectionResult.success) {
return 'connected-color';
} else {
return 'disconnected-color';
}
});
const fullDisplayServerName = computed(() => {
return connectionResult.serverInfo.name + '/' + connectionResult.serverInfo.version;
});
@ -88,9 +75,13 @@ function toggleConnectionPanel() {
</script>
<style>
.connected .status-circle {
background-color: var(--el-color-success) !important;
}
.connected-color {
background-color: #21DA49;
.connected .connect-status {
border: 1px solid var(--el-color-success) !important;
color: var(--el-color-success) !important;
}
.disconnected-color {
@ -100,8 +91,8 @@ function toggleConnectionPanel() {
.status-circle {
height: 12px;
width: 12px;
margin-right: 8px;
border-radius: 99%;
background-color: var(--main-color);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
}
@ -123,7 +114,13 @@ function toggleConnectionPanel() {
.connected-status-container .connect-status {
display: flex;
align-items: center;
margin-top: 20px;
justify-content: space-between;
margin-top: 10px;
border-radius: .5em;
padding: 5px 10px;
width: 30px;
border: 1px solid var(--main-color);
color: var(--main-color);
}
.connected-status-container:hover {
@ -148,7 +145,10 @@ function toggleConnectionPanel() {
.mcp-server-info .name {
font-size: 14px;
font-weight: 600;
max-width: 60px;
width: 30px;
display: flex;
align-items: center;
justify-content: center;
white-space: wrap;
background-color: #f39a6d;
padding: 5px 12px;

View File

@ -35,6 +35,8 @@ function clickLogo() {
.simple-logo {
height: 73px;
display: flex;
align-items: center;
flex-direction: column;
user-select: none;
-webkit-user-drag: none;
@ -47,7 +49,7 @@ function clickLogo() {
}
.simple-logo .iconfont {
font-size: 48px;
font-size: 38px;
}
</style>

View File

@ -1,3 +1,5 @@
import { reactive } from "vue";
interface TypeAble {
type: string;
}
@ -27,4 +29,8 @@ export function normaliseJavascriptType(type: string) {
default:
return 'string';
}
}
}
export const mcpSetting = reactive({
timeout: 60,
});

View File

@ -3,6 +3,7 @@ import { llmManager, llms } from "@/views/setting/llm";
import { pinkLog } from "@/views/setting/util";
import I18n from '@/i18n/index';
import { userHasReadGuide } from "@/components/guide/tour";
import { mcpSetting } from "./mcp";
export async function loadSetting() {
const bridge = useMessageBridge();
@ -14,10 +15,11 @@ export async function loadSetting() {
} else {
const persistConfig = data.msg;
pinkLog('配置加载成功');
pinkLog('配置加载成功');
llmManager.currentModelIndex = persistConfig.MODEL_INDEX;
I18n.global.locale.value = persistConfig.LANG;
llmManager.currentModelIndex = persistConfig.MODEL_INDEX || 0;
I18n.global.locale.value = persistConfig.LANG || 'zh';
mcpSetting.timeout = persistConfig.MCP_TIMEOUT_SEC || 60;
persistConfig.LLM_INFO.forEach((element: any) => {
llms.push(element);
@ -48,7 +50,8 @@ export function saveSetting(saveHandler?: () => void) {
const saveConfig = {
MODEL_INDEX: llmManager.currentModelIndex,
LLM_INFO: JSON.parse(JSON.stringify(llms)),
LANG: I18n.global.locale.value
LANG: I18n.global.locale.value,
MCP_TIMEOUT_SEC: mcpSetting.timeout
};
bridge.addCommandListener('setting/save', data => {

View File

@ -21,6 +21,8 @@ export interface Content {
uri: string;
mimeType: string;
text: string;
blob?: string;
[key: string]: any;
}
export interface MessageContent {
@ -59,12 +61,18 @@ export interface ResourceTemplate {
description: string;
}
export interface Resources {
uri: string;
name: string;
mimeType: string;
}
export interface ResourceTemplatesListResponse {
resourceTemplates: ResourceTemplate[]
}
export interface ResourcesListResponse {
resources: any[]; // 根据示例返回空数组,可进一步定义具体类型
resources: Resources[]
}
export interface ResourcesReadResponse {

View File

@ -64,6 +64,12 @@ export async function getBlobUrlByFilename(filename: string) {
mimeType = 'image/svg+xml';
break;
}
const blobUrl = getImageBlobUrlByBase64(base64String, mimeType, filename);
return blobUrl;
}
export function getImageBlobUrlByBase64(base64String: string, mimeType: string, cacheKey?: string) {
const byteCharacters = atob(base64String);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
@ -73,6 +79,8 @@ export async function getBlobUrlByFilename(filename: string) {
const blob = new Blob([byteArray], { type: mimeType });
const blobUrl = URL.createObjectURL(blob);
// 将结果存入缓存
blobUrlCache.set(filename, blobUrl);
if (cacheKey) {
blobUrlCache.set(cacheKey, blobUrl);
}
return blobUrl;
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "عنوان prompt المخصص",
"enter-message-dot": "أدخل الرسالة...",
"generate-answer": "جارٍ إنشاء الإجابة",
"choose-presetting": "اختر الإعداد المسبق"
"choose-presetting": "اختر الإعداد المسبق",
"cwd": "دليل التنفيذ",
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "Titel für benutzerdefinierte Eingabeaufforderung",
"enter-message-dot": "Nachricht eingeben...",
"generate-answer": "Antwort wird generiert",
"choose-presetting": "Voreinstellung auswählen"
"choose-presetting": "Voreinstellung auswählen",
"cwd": "Ausführungsverzeichnis",
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "Title for custom prompt",
"enter-message-dot": "Enter message...",
"generate-answer": "Generating answer",
"choose-presetting": "Select preset"
"choose-presetting": "Select preset",
"cwd": "Execution directory",
"mcp-server-timeout": "Maximum call time of MCP tool"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage"
"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"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "カスタムプロンプトのタイトル",
"enter-message-dot": "メッセージを入力...",
"generate-answer": "回答を生成中",
"choose-presetting": "プリセットを選択"
"choose-presetting": "プリセットを選択",
"cwd": "実行ディレクトリ",
"mcp-server-timeout": "MCPツールの最大呼び出し時間"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "사용자 지정 프롬프트 제목",
"enter-message-dot": "메시지를 입력하세요...",
"generate-answer": "답변 생성 중",
"choose-presetting": "프리셋 선택"
"choose-presetting": "프리셋 선택",
"cwd": "실행 디렉터리",
"mcp-server-timeout": "MCP 도구 최대 호출 시간"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "Заголовок пользовательского prompt",
"enter-message-dot": "Введите сообщение...",
"generate-answer": "Генерация ответа",
"choose-presetting": "Выбрать预设"
"choose-presetting": "Выбрать预设",
"cwd": "Каталог выполнения",
"mcp-server-timeout": "Максимальное время вызова инструмента MCP"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "输入自定义 prompt 的标题",
"enter-message-dot": "输入消息...",
"generate-answer": "正在生成答案",
"choose-presetting": "选择预设"
"choose-presetting": "选择预设",
"cwd": "执行目录",
"mcp-server-timeout": "MCP工具最长调用时间"
}

View File

@ -149,5 +149,7 @@
"add-system-prompt.name-placeholder": "自定義提示的標題",
"enter-message-dot": "輸入訊息...",
"generate-answer": "正在生成答案",
"choose-presetting": "選擇預設"
"choose-presetting": "選擇預設",
"cwd": "執行目錄",
"mcp-server-timeout": "MCP工具最長調用時間"
}

View File

@ -6,7 +6,7 @@
</span>
<p>
OpenMCP Client 0.0.5 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
OpenMCP Client 0.0.7 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
</p>
<p>

View File

@ -6,13 +6,13 @@
<el-form :model="connectionArgs" :rules="rules" ref="stdioForm">
<el-form-item prop="commandString">
<div class="input-with-label">
<span class="input-label">命令</span>
<span class="input-label">{{ t("command") }}</span>
<el-input v-model="connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
</div>
</el-form-item>
<el-form-item prop="cwd">
<div class="input-with-label">
<span class="input-label">执行目录</span>
<span class="input-label">{{ t('cwd') }}</span>
<el-input v-model="connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
</div>
</el-form-item>

View File

@ -139,6 +139,7 @@ function chooseDebugMode(index: number) {
.debug-welcome {
user-select: none;
height: 80%;
}
.debug-option.disable {

View File

@ -14,6 +14,19 @@
</el-select>
</div>
</div>
<div class="setting-option">
<span>
<span class="iconfont icon-timeout"></span>
<span class="option-title">{{ t('mcp-server-timeout') }} (sec)</span>
</span>
<div style="width: 200px;">
<el-slider
v-model="mcpSetting.timeout"
:min="10" :max="10000" :step="1"
@change="safeSaveSetting" />
</div>
</div>
</div>
</template>
@ -21,6 +34,8 @@
import { defineComponent, ref } from 'vue';
import { languageSetting } from './language';
import { useI18n } from 'vue-i18n';
import { mcpSetting } from '@/hook/mcp';
import { debounce } from 'lodash';
import { saveSetting } from '@/hook/setting';
defineComponent({ name: 'appearance' });
@ -29,15 +44,25 @@ const { t, locale } = useI18n();
const currentLanguage = ref('简体中文');
function onlanguagechange(code: string) {
console.log('enter lang change');
const option = languageSetting.options.find(item => item.value === code);
if (option) {
currentLanguage.value = option.text;
}
// languageDialogShow.value = true;
saveSetting();
saveSetting();
}
const safeSaveSetting = debounce(() => {
saveSetting();
}, 10);
</script>
<style></style>

View File

@ -97,6 +97,8 @@ export class ClientController {
}
const resource = await client.readResource(option.resourceUri);
console.log(resource);
return {
code: 200,
msg: resource
@ -130,7 +132,8 @@ export class ClientController {
const toolResult = await client.callTool({
name: option.toolName,
arguments: option.toolArgs
arguments: option.toolArgs,
callToolOption: option.callToolOption
});
// console.log(JSON.stringify(toolResult, null, 2));

View File

@ -41,7 +41,8 @@ export class McpClient {
this.transport = new StdioClientTransport({
command: this.options.command || '',
args: this.options.args || [],
cwd: this.options.cwd || process.cwd()
cwd: this.options.cwd || process.cwd(),
stderr: 'pipe'
});
break;
@ -117,8 +118,10 @@ export class McpClient {
}
// 调用工具
public async callTool(options: { name: string; arguments: Record<string, any> }) {
return await this.client.callTool(options);
public async callTool(options: { name: string; arguments: Record<string, any>, callToolOption?: any }) {
const { callToolOption, ...methodArgs } = options;
return await this.client.callTool(methodArgs, undefined, callToolOption);
}
}

View File

@ -25,7 +25,7 @@ function getDefaultLanguage() {
const DEFAULT_CONFIG: IConfig = {
MODEL_INDEX: 0,
LLM_INFO: llms,
LANG: getDefaultLanguage()
LANG: getDefaultLanguage(), MCP_TIMEOUT_SEC: 60
};

View File

@ -1,4 +1,12 @@
{
"currentIndex": 0,
"tabs": []
"tabs": [
{
"name": "Blank test 1",
"icon": "icon-blank",
"type": "blank",
"componentIndex": -1,
"storage": {}
}
]
}