支持 resources/list 的调度
This commit is contained in:
parent
c716cbc6ca
commit
8d549d17e2
@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## [main] 0.0.7
|
||||
- 优化页面布局,使得调试内容更加紧凑
|
||||
- 扩大默认的上下文长度
|
||||
- 增加「通用选项」,用于设置mcp服务器的最大的等待时间
|
||||
|
||||
## [main] 0.0.6
|
||||
- 修复部分因为服务器名称特殊字符而导致的保存实效的错误
|
||||
|
@ -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.
@ -94,7 +94,7 @@ onMounted(async () => {
|
||||
|
||||
<style>
|
||||
.main {
|
||||
height: calc(100vh - 50px);
|
||||
height: calc(100vh - 5px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ if (!tabStorage.settings) {
|
||||
enableTools: [],
|
||||
enableWebSearch: false,
|
||||
temperature: 0.7,
|
||||
contextLength: 10,
|
||||
contextLength: 20,
|
||||
systemPrompt: ''
|
||||
} as ChatSetting;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听资源变化重置表单
|
||||
|
@ -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;
|
173
renderer/src/components/main-panel/resource/resource-list.vue
Normal file
173
renderer/src/components/main-panel/resource/resource-list.vue
Normal 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>
|
@ -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>
|
@ -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>;
|
||||
|
@ -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, () => {
|
||||
|
@ -15,6 +15,11 @@
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- resources/list -->
|
||||
</div>
|
||||
|
||||
<div class="tool-list-function-container">
|
||||
<el-button
|
||||
type="primary"
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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>
|
@ -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,
|
||||
});
|
@ -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 => {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "عنوان prompt المخصص",
|
||||
"enter-message-dot": "أدخل الرسالة...",
|
||||
"generate-answer": "جارٍ إنشاء الإجابة",
|
||||
"choose-presetting": "اختر الإعداد المسبق"
|
||||
"choose-presetting": "اختر الإعداد المسبق",
|
||||
"cwd": "دليل التنفيذ",
|
||||
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "カスタムプロンプトのタイトル",
|
||||
"enter-message-dot": "メッセージを入力...",
|
||||
"generate-answer": "回答を生成中",
|
||||
"choose-presetting": "プリセットを選択"
|
||||
"choose-presetting": "プリセットを選択",
|
||||
"cwd": "実行ディレクトリ",
|
||||
"mcp-server-timeout": "MCPツールの最大呼び出し時間"
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "사용자 지정 프롬프트 제목",
|
||||
"enter-message-dot": "메시지를 입력하세요...",
|
||||
"generate-answer": "답변 생성 중",
|
||||
"choose-presetting": "프리셋 선택"
|
||||
"choose-presetting": "프리셋 선택",
|
||||
"cwd": "실행 디렉터리",
|
||||
"mcp-server-timeout": "MCP 도구 최대 호출 시간"
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "Заголовок пользовательского prompt",
|
||||
"enter-message-dot": "Введите сообщение...",
|
||||
"generate-answer": "Генерация ответа",
|
||||
"choose-presetting": "Выбрать预设"
|
||||
"choose-presetting": "Выбрать预设",
|
||||
"cwd": "Каталог выполнения",
|
||||
"mcp-server-timeout": "Максимальное время вызова инструмента MCP"
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "输入自定义 prompt 的标题",
|
||||
"enter-message-dot": "输入消息...",
|
||||
"generate-answer": "正在生成答案",
|
||||
"choose-presetting": "选择预设"
|
||||
"choose-presetting": "选择预设",
|
||||
"cwd": "执行目录",
|
||||
"mcp-server-timeout": "MCP工具最长调用时间"
|
||||
}
|
@ -149,5 +149,7 @@
|
||||
"add-system-prompt.name-placeholder": "自定義提示的標題",
|
||||
"enter-message-dot": "輸入訊息...",
|
||||
"generate-answer": "正在生成答案",
|
||||
"choose-presetting": "選擇預設"
|
||||
"choose-presetting": "選擇預設",
|
||||
"cwd": "執行目錄",
|
||||
"mcp-server-timeout": "MCP工具最長調用時間"
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -139,6 +139,7 @@ function chooseDebugMode(index: number) {
|
||||
|
||||
.debug-welcome {
|
||||
user-select: none;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
.debug-option.disable {
|
||||
|
@ -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>
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ function getDefaultLanguage() {
|
||||
const DEFAULT_CONFIG: IConfig = {
|
||||
MODEL_INDEX: 0,
|
||||
LLM_INFO: llms,
|
||||
LANG: getDefaultLanguage()
|
||||
LANG: getDefaultLanguage(), MCP_TIMEOUT_SEC: 60
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,4 +1,12 @@
|
||||
{
|
||||
"currentIndex": 0,
|
||||
"tabs": []
|
||||
"tabs": [
|
||||
{
|
||||
"name": "Blank test 1",
|
||||
"icon": "icon-blank",
|
||||
"type": "blank",
|
||||
"componentIndex": -1,
|
||||
"storage": {}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user