Compare commits
14 Commits
feat/opena
...
main
Author | SHA1 | Date | |
---|---|---|---|
7167f5bbd8 | |||
![]() |
e07840fd02 | ||
268eb54ee3 | |||
![]() |
535ca9bf56 | ||
0d6bb121fc | |||
65adc6f8d5 | |||
0883aa9d64 | |||
229297cfac | |||
f5683dd52a | |||
f6c410a231 | |||
b6c81342a9 | |||
644d537ec3 | |||
48b60b059d | |||
ea0779e0ec |
2
.gitignore
vendored
@ -126,7 +126,7 @@ dist
|
|||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
.vscode-test
|
.openmcp-test
|
||||||
|
|
||||||
# yarn v2
|
# yarn v2
|
||||||
.yarn/cache
|
.yarn/cache
|
||||||
|
@ -111,11 +111,12 @@ function makeHomeAnimation() {
|
|||||||
|
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{ selector: ".bilibili-player-container", start: "top 65%", end: "top 65%" },
|
{ selector: "#home-0", trigger: '#home-0', start: "top 65%", end: "top 65%" },
|
||||||
{ selector: "#openmcp-为谁准备", trigger: '#openmcp-为谁准备', start: "top 65%", end: "top 65%" },
|
{ selector: ".bilibili-player-container", trigger: '#home-0', start: "top 65%", end: "top 65%" },
|
||||||
{ selector: ".k-tabs", trigger: '#openmcp-为谁准备', start: "top 65%", end: "top 65%" },
|
{ selector: "#home-1", trigger: '#home-1', start: "top 65%", end: "top 65%" },
|
||||||
{ selector: "#问题解答-faq", trigger: '#问题解答-faq', start: "top 65%", end: "top 65%" },
|
{ selector: ".k-tabs", trigger: '#home-1', start: "top 65%", end: "top 65%" },
|
||||||
{ selector: ".el-collapse", trigger: '#问题解答-faq', start: "top 65%", end: "top 65%" },
|
{ selector: "#home-2", trigger: '#home-2', start: "top 65%", end: "top 65%" },
|
||||||
|
{ selector: ".el-collapse", trigger: '#home-2', start: "top 65%", end: "top 65%" },
|
||||||
];
|
];
|
||||||
|
|
||||||
elements.forEach(element => {
|
elements.forEach(element => {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="k-tabs" :style="panelStyle">
|
<div class="k-tabs">
|
||||||
<div class="k-tabs-tags">
|
<div class="k-tabs-tags">
|
||||||
<div class="k-tabs-tag-item" v-for="pane of tabsContainer.paneInfos" :key="pane.id"
|
<div class="k-tabs-tag-item" v-for="pane of tabsContainer.paneInfos" :key="pane.id"
|
||||||
:ref="el => tabsContainer.getPanes(el, pane.id)" @click="tabsContainer.switchLabel(pane.id)"
|
:ref="el => tabsContainer.getPanes(el, pane.id)" @click="tabsContainer.switchLabel(pane.id)" :class="{
|
||||||
:class="{ 'active-tab': tabsContainer.lastPaneId === pane.id }">
|
'active-tab': tabsContainer.lastPaneId === pane.id,
|
||||||
|
'prev-tab': pane.id === prevActiveIndex
|
||||||
|
}">
|
||||||
<span :class="pane.labelClass"></span>
|
<span :class="pane.labelClass"></span>
|
||||||
<span>{{ pane.label }}</span>
|
<span>{{ pane.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -15,7 +17,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, useSlots, provide, nextTick, computed, onMounted } from 'vue';
|
import { reactive, useSlots, nextTick, onMounted, ref } from 'vue';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
type PaneInfo = {
|
type PaneInfo = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -23,116 +26,96 @@ type PaneInfo = {
|
|||||||
labelClass: string;
|
labelClass: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TabsContainer {
|
|
||||||
paneInfos: PaneInfo[];
|
|
||||||
panes: HTMLElement[];
|
|
||||||
lastPaneId?: number;
|
|
||||||
activeLabel: string;
|
|
||||||
panelContainer?: any;
|
|
||||||
height: string;
|
|
||||||
getPanes: (el: any, id: string | number) => void;
|
|
||||||
switchLabel: (id: number) => void;
|
|
||||||
updateLabels: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
let maxChildHeight = 0;
|
const prevActiveIndex = ref<number | null>(null);
|
||||||
|
|
||||||
function resizeTab(id: number) {
|
const tabsContainer = reactive({
|
||||||
const container = tabsContainer.panelContainer;
|
paneInfos: [] as PaneInfo[],
|
||||||
if (container) {
|
panes: [] as HTMLElement[],
|
||||||
const panels = Array.from(container.children) as HTMLElement[];
|
|
||||||
|
|
||||||
const currentPanel = panels[id];
|
|
||||||
if (currentPanel) {
|
|
||||||
maxChildHeight = Math.max(maxChildHeight, currentPanel.clientHeight);
|
|
||||||
tabsContainer.height = maxChildHeight + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const panelStyle = computed(() => ({
|
|
||||||
height: tabsContainer.height
|
|
||||||
}));
|
|
||||||
|
|
||||||
const tabsContainer: TabsContainer = reactive({
|
|
||||||
paneInfos: [],
|
|
||||||
panes: [],
|
|
||||||
lastPaneId: 0,
|
lastPaneId: 0,
|
||||||
activeLabel: '',
|
panelContainer: undefined as HTMLElement | undefined,
|
||||||
hoverBar: null,
|
|
||||||
panelContainer: undefined,
|
getPanes(el: HTMLElement | null, id: number) {
|
||||||
height: '0',
|
if (el) this.panes[id] = el;
|
||||||
getPanes(el: HTMLElement | null, id: string | number) {
|
|
||||||
if (el) {
|
|
||||||
this.panes[id] = el;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
switchLabel(id: number) {
|
|
||||||
if (this.lastPaneId === id) {
|
async switchLabel(id: number) {
|
||||||
return;
|
if (this.lastPaneId === id) return;
|
||||||
}
|
|
||||||
|
const oldId = this.lastPaneId;
|
||||||
|
prevActiveIndex.value = oldId;
|
||||||
this.lastPaneId = id;
|
this.lastPaneId = id;
|
||||||
this.activeLabel = this.paneInfos[id]?.label || '';
|
|
||||||
|
|
||||||
const container = tabsContainer.panelContainer;
|
const container = this.panelContainer;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
const panels = Array.from(container.children) as HTMLElement[];
|
const panels = Array.from(container.children) as HTMLElement[];
|
||||||
|
const oldPanel = panels[oldId];
|
||||||
|
const newPanel = panels[id];
|
||||||
|
|
||||||
// 进行动画切换
|
// 1. First shrink the old panel to 0.3 scale
|
||||||
|
if (oldPanel) {
|
||||||
|
await gsap.to(oldPanel, {
|
||||||
|
scale: 0.3,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "power2.in"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Hide all other panels
|
||||||
panels.forEach((panel, index) => {
|
panels.forEach((panel, index) => {
|
||||||
console.log('index', index);
|
if (index !== id) {
|
||||||
console.log('id', id);
|
gsap.set(panel, {
|
||||||
|
display: 'none',
|
||||||
panel.style.transition = 'opacity 0.3s ease';
|
opacity: 0,
|
||||||
if (index === id) {
|
scale: 1
|
||||||
panel.style.display = 'block';
|
});
|
||||||
setTimeout(() => {
|
|
||||||
panel.style.opacity = '1';
|
|
||||||
}, 150);
|
|
||||||
} else {
|
|
||||||
panel.style.opacity = '0';
|
|
||||||
setTimeout(() => {
|
|
||||||
panel.style.display = 'none';
|
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 3. Show and animate in the new panel
|
||||||
|
gsap.set(newPanel, {
|
||||||
|
display: 'block',
|
||||||
|
opacity: 0,
|
||||||
|
scale: 1.2
|
||||||
|
});
|
||||||
|
|
||||||
nextTick(() => {
|
gsap.to(newPanel, {
|
||||||
resizeTab(id);
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
duration: 0.4,
|
||||||
|
ease: "back.out(1.2)"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateLabels() {
|
|
||||||
const defaultChildren = slots.default?.() || [];
|
|
||||||
|
|
||||||
this.paneInfos = [];
|
updateLabels() {
|
||||||
for (const index in defaultChildren) {
|
this.paneInfos = (slots.default?.() || []).map((vnode, index) => ({
|
||||||
const vnode = defaultChildren[index];
|
id: index,
|
||||||
this.paneInfos.push({
|
|
||||||
id: Number(index),
|
|
||||||
label: vnode.props?.label || '',
|
label: vnode.props?.label || '',
|
||||||
labelClass: vnode.props?.labelClass || '',
|
labelClass: vnode.props?.labelClass || '',
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
if (this.paneInfos.length > 0) {
|
|
||||||
this.activeLabel = this.paneInfos[0]?.label || '';
|
|
||||||
nextTick(() => {
|
|
||||||
resizeTab(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize
|
||||||
tabsContainer.updateLabels();
|
tabsContainer.updateLabels();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (tabsContainer.panelContainer) {
|
if (tabsContainer.panelContainer) {
|
||||||
const panels = Array.from(tabsContainer.panelContainer.children) as HTMLElement[];
|
const panels = Array.from(tabsContainer.panelContainer.children) as HTMLElement[];
|
||||||
panels.forEach((panel, index) => {
|
panels.forEach((panel, index) => {
|
||||||
panel.style.position = 'absolute';
|
panel.style.position = 'absolute';
|
||||||
|
panel.style.width = '100%';
|
||||||
|
|
||||||
if (index != tabsContainer.lastPaneId) {
|
if (index !== 0) {
|
||||||
panel.style.display = 'none';
|
panel.style.display = 'none';
|
||||||
panel.style.opacity = '0';
|
} else {
|
||||||
|
gsap.from(panel, {
|
||||||
|
scale: 1.2,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.5
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -142,58 +125,43 @@ onMounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.k-tabs {
|
.k-tabs {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: fit-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-tabs-tags {
|
.k-tabs-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-tabs-tag-item {
|
.k-tabs-tag-item {
|
||||||
background-color: var(--vp-button-alt-bg);
|
background-color: var(--vp-button-alt-bg);
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
margin-right: 10px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.k-tabs-tag-item {
|
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
font-size: 1rem;
|
cursor: pointer;
|
||||||
}
|
transition: all 0.3s ease;
|
||||||
}
|
transform-origin: center;
|
||||||
|
will-change: transform;
|
||||||
.k-tabs-tag-item:hover {
|
|
||||||
background-color: var(--vp-c-brand-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-tabs-tag-item.active-tab {
|
.k-tabs-tag-item.active-tab {
|
||||||
background-color: var(--vp-c-brand-3);
|
background-color: var(--vp-c-brand-3);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 414px) {
|
.k-tabs-content {
|
||||||
.k-tabs-tags {
|
position: relative;
|
||||||
}
|
min-height: 200px;
|
||||||
}
|
|
||||||
|
|
||||||
.hover-bar {
|
|
||||||
background-color: var(--vp-c-brand-3);
|
|
||||||
border-radius: .9em .9em 0 0;
|
|
||||||
transition: .35s ease-in-out;
|
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-tabs-content>* {
|
.k-tabs-content>* {
|
||||||
transition: opacity 0.3s ease;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,22 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="bilibili-player-container">
|
<div class="bilibili-player-container" @mouseenter="onHover" @mouseleave="onHoverEnd">
|
||||||
<iframe v-if="isPlaying" :src="playerUrl" frameborder="0" allowfullscreen></iframe>
|
<iframe v-if="isPlaying" :src="playerUrl" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
<div v-else class="cover-container" @click="playVideo">
|
<div v-else class="cover-container" @click="playVideo">
|
||||||
<img :src="props.cover" class="cover-image" />
|
<img :src="props.cover" class="cover-image" ref="coverImage" />
|
||||||
<button class="play-button">
|
<button class="play-button" ref="playButton">
|
||||||
<svg viewBox="0 0 24 24" width="48" height="48">
|
<svg viewBox="0 0 24 24" width="48" height="48">
|
||||||
<path fill="currentColor" d="M8 5v14l11-7z" />
|
<path fill="currentColor" d="M8 5v14l11-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="hover-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
import gsap from 'gsap'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
@ -31,10 +33,56 @@ const props = defineProps({
|
|||||||
|
|
||||||
const isPlaying = ref(false);
|
const isPlaying = ref(false);
|
||||||
const playerUrl = ref(props.url);
|
const playerUrl = ref(props.url);
|
||||||
|
const coverImage = ref(null);
|
||||||
|
const playButton = ref(null);
|
||||||
|
|
||||||
function playVideo() {
|
function playVideo() {
|
||||||
isPlaying.value = true
|
isPlaying.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHover() {
|
||||||
|
if (!isPlaying.value) {
|
||||||
|
gsap.to(coverImage.value, {
|
||||||
|
filter: 'brightness(0.7) saturate(1.2)',
|
||||||
|
duration: 0.3,
|
||||||
|
ease: 'power2.out'
|
||||||
|
});
|
||||||
|
gsap.to(playButton.value, {
|
||||||
|
scale: 1.15,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: 'elastic.out(1, 0.5)'
|
||||||
|
});
|
||||||
|
gsap.to('.hover-overlay', {
|
||||||
|
opacity: 1,
|
||||||
|
duration: 0.3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHoverEnd() {
|
||||||
|
if (!isPlaying.value) {
|
||||||
|
gsap.to(coverImage.value, {
|
||||||
|
filter: 'brightness(1) saturate(1)',
|
||||||
|
duration: 0.3,
|
||||||
|
ease: 'power2.out'
|
||||||
|
});
|
||||||
|
gsap.to(playButton.value, {
|
||||||
|
scale: 1,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: 'power2.out'
|
||||||
|
});
|
||||||
|
gsap.to('.hover-overlay', {
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始状态设置
|
||||||
|
gsap.set(coverImage.value, { filter: 'brightness(1) saturate(1)' });
|
||||||
|
gsap.set('.hover-overlay', { opacity: 0 });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -54,7 +102,13 @@ function playVideo() {
|
|||||||
aspect-ratio: 16/9;
|
aspect-ratio: 16/9;
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 3px solid var(--vp-c-brand-2);
|
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili-player-container:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cover-container {
|
.cover-container {
|
||||||
@ -68,6 +122,19 @@ function playVideo() {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
will-change: filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, rgba(var(--vp-c-brand-2-rgb), 0.2) 0%, rgba(var(--vp-c-brand-1-rgb), 0.1) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-button {
|
.play-button {
|
||||||
@ -85,16 +152,24 @@ function playVideo() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: transform 0.2s ease, background-color 0.2s ease;
|
transition: transform 0.3s ease, background-color 0.3s ease;
|
||||||
|
will-change: transform;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-button:hover {
|
.play-button:hover {
|
||||||
transform: translate(-50%, -50%) scale(1.1);
|
background-color: var(--vp-c-brand-1);
|
||||||
background-color: var(--vp-c-brand-2);
|
transform: translate(-50%, -50%) scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-button svg {
|
.play-button svg {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button:hover svg {
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
@ -102,4 +177,24 @@ iframe {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.bilibili-player-container {
|
||||||
|
min-width: 90vw;
|
||||||
|
min-height: calc(90vw * 9 / 16);
|
||||||
|
width: 90vw;
|
||||||
|
height: calc(90vw * 9 / 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button svg {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="height: fit-content; width: fit-content; position: relative;" ref="container">
|
<div style="height: fit-content; width: fit-content; position: relative;" ref="container">
|
||||||
<svg class="VPImage image-src" viewBox="0 0 612 612" fill="none" xmlns="http://www.w3.org/2000/svg" ref="svgElement">
|
<svg class="VPImage image-src" viewBox="0 0 612 612" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
ref="svgElement">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="gradient_1" gradientUnits="userSpaceOnUse" x1="300" y1="0" x2="300" y2="600">
|
<linearGradient id="gradient_1" gradientUnits="userSpaceOnUse" x1="300" y1="0" x2="300" y2="600">
|
||||||
<stop offset="0" stop-color="#A1A7F6" />
|
<stop offset="0" stop-color="#A1A7F6" />
|
||||||
@ -58,6 +59,11 @@
|
|||||||
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
|
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
|
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
|
||||||
</filter>
|
</filter>
|
||||||
|
<!-- 新增描边渐变色定义 -->
|
||||||
|
<linearGradient id="strokeGradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#A48BF7" />
|
||||||
|
<stop offset="80%" stop-color="#F7F6FD" />
|
||||||
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<g transform="translate(6 2)">
|
<g transform="translate(6 2)">
|
||||||
<g>
|
<g>
|
||||||
@ -87,67 +93,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap'
|
||||||
|
|
||||||
const container = ref(null)
|
const container = ref < any > (null);
|
||||||
const svgElement = ref(null)
|
const svgElement = ref < any > (null);
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const paths = svgElement.value.querySelectorAll('path')
|
const paths = svgElement.value.querySelectorAll('path');
|
||||||
|
// 标题文字
|
||||||
|
const nameClip = document.querySelector('.VPHero .heading .name.clip');
|
||||||
|
const headingText = document.querySelector('.VPHero .heading .text');
|
||||||
|
const tagline = document.querySelector('.VPHero .tagline');
|
||||||
|
const actions = document.querySelector('.VPHero .actions');
|
||||||
|
|
||||||
|
// 设置首页各个元素的初始状态
|
||||||
|
gsap.set(nameClip, {
|
||||||
|
transform: 'translate(50px, 80px)',
|
||||||
|
scale: 1.8,
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.set([headingText, tagline, actions], {
|
||||||
|
opacity: 0,
|
||||||
|
y: 20
|
||||||
|
});
|
||||||
|
|
||||||
// 设置初始状态
|
|
||||||
gsap.set(paths, {
|
gsap.set(paths, {
|
||||||
strokeDasharray: (i, target) => {
|
strokeDasharray: (_, target) => {
|
||||||
const length = target.getTotalLength()
|
const length = target.getTotalLength()
|
||||||
target.style.strokeDasharray = length
|
target.style.strokeDasharray = length
|
||||||
return length
|
return length
|
||||||
},
|
},
|
||||||
strokeDashoffset: (i, target) => target.getTotalLength(),
|
strokeDashoffset: (_, target) => target.getTotalLength(),
|
||||||
stroke: 'var(--vp-c-brand-2)',
|
stroke: 'url(#strokeGradient)', // 使用渐变色描边
|
||||||
strokeWidth: 3,
|
strokeWidth: 3.5,
|
||||||
fillOpacity: 0
|
fillOpacity: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建主时间线
|
|
||||||
const master = gsap.timeline()
|
|
||||||
|
|
||||||
// 描边动画时间线
|
|
||||||
const drawTimeline = gsap.timeline({
|
|
||||||
defaults: { duration: 1.5, ease: "power2.inOut" }
|
|
||||||
})
|
|
||||||
|
|
||||||
// 为所有路径添加描边动画
|
|
||||||
paths.forEach(path => {
|
|
||||||
drawTimeline.to(path, {
|
|
||||||
strokeDashoffset: 0,
|
|
||||||
}, "<+=0.3")
|
|
||||||
})
|
|
||||||
|
|
||||||
// 反向描边消失动画时间线
|
|
||||||
const reverseTimeline = gsap.timeline({
|
|
||||||
defaults: { duration: 1.5, ease: "power2.inOut" }
|
|
||||||
})
|
|
||||||
|
|
||||||
// 为所有路径添加反向描边动画
|
|
||||||
paths.forEach(path => {
|
|
||||||
reverseTimeline.to(path, {
|
|
||||||
fillOpacity: 1,
|
|
||||||
strokeDashoffset: (i, target) => target.getTotalLength()
|
|
||||||
}, "<")
|
|
||||||
})
|
|
||||||
|
|
||||||
// 微扰动画时间线
|
|
||||||
const wiggleTimeline = gsap.timeline({
|
|
||||||
repeat: -1,
|
|
||||||
yoyo: true,
|
|
||||||
defaults: {
|
|
||||||
duration: 2,
|
|
||||||
ease: "sine.inOut"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 为每个圆形元素创建不同的微扰动画
|
// 为每个圆形元素创建不同的微扰动画
|
||||||
const wiggleElements = [
|
const wiggleElements = [
|
||||||
{
|
{
|
||||||
@ -166,7 +150,55 @@ onMounted(() => {
|
|||||||
el: svgElement.value.querySelector('[transform="translate(388 129)"]'),
|
el: svgElement.value.querySelector('[transform="translate(388 129)"]'),
|
||||||
x: "+=9", y: "+=4", rotation: "-=2"
|
x: "+=9", y: "+=4", rotation: "-=2"
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
|
// 创建主时间线
|
||||||
|
const master = gsap.timeline();
|
||||||
|
|
||||||
|
// 描边动画时间线
|
||||||
|
const drawTimeline = gsap.timeline({
|
||||||
|
defaults: { duration: 1.5, ease: "power2.inOut" }
|
||||||
|
});
|
||||||
|
|
||||||
|
// OpenMCP 标题入场动画
|
||||||
|
if (!nameClip || !headingText || !tagline || !actions) {
|
||||||
|
console.error("One or more required elements are missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTimeline.to(nameClip, {
|
||||||
|
transform: 'translate(50px, 80px)',
|
||||||
|
}, "<+=0.3");
|
||||||
|
|
||||||
|
// 右侧 OpenMCP 图标描边动画
|
||||||
|
paths.forEach((path: gsap.TweenTarget) => {
|
||||||
|
drawTimeline.to(path, {
|
||||||
|
strokeDashoffset: 0,
|
||||||
|
}, "<+=0.3");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 反向描边消失动画时间线
|
||||||
|
const reverseTimeline = gsap.timeline({
|
||||||
|
defaults: { duration: 1.5, ease: "power2.inOut" }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 为所有路径添加反向描边动画
|
||||||
|
paths.forEach(path => {
|
||||||
|
reverseTimeline.to(path, {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeDashoffset: (i, target) => target.getTotalLength()
|
||||||
|
}, "<")
|
||||||
|
});
|
||||||
|
|
||||||
|
// 微扰动画时间线
|
||||||
|
const wiggleTimeline = gsap.timeline({
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
defaults: {
|
||||||
|
duration: 2,
|
||||||
|
ease: "sine.inOut"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 为每个元素添加独特的微扰动画
|
// 为每个元素添加独特的微扰动画
|
||||||
wiggleElements.forEach(item => {
|
wiggleElements.forEach(item => {
|
||||||
@ -174,15 +206,90 @@ onMounted(() => {
|
|||||||
x: item.x,
|
x: item.x,
|
||||||
y: item.y,
|
y: item.y,
|
||||||
rotation: item.rotation,
|
rotation: item.rotation,
|
||||||
scale: item.scale,
|
|
||||||
transformOrigin: "center center"
|
transformOrigin: "center center"
|
||||||
}, "<")
|
}, "<")
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// 创建主时间线
|
||||||
|
const heroTimeline = gsap.timeline({
|
||||||
|
defaults: {
|
||||||
|
duration: 0.8,
|
||||||
|
ease: "power3.out"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加动画序列
|
||||||
|
heroTimeline
|
||||||
|
.to(nameClip, {
|
||||||
|
transform: 'translate(0, 0)',
|
||||||
|
scale: 1,
|
||||||
|
duration: 1.2,
|
||||||
|
ease: "back.out(1.7)"
|
||||||
|
})
|
||||||
|
.to([headingText, tagline, actions], {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
stagger: 0.15,
|
||||||
|
duration: 0.6
|
||||||
|
}, "-=0.4"); // 与上一个动画重叠0.4秒
|
||||||
|
|
||||||
|
|
||||||
|
// 下方三个 feature 的动画
|
||||||
|
const boxTimeline = gsap.timeline({
|
||||||
|
defaults: {
|
||||||
|
duration: 0.8,
|
||||||
|
ease: "power3.out"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取所有box元素
|
||||||
|
const boxes = document.querySelectorAll('.VPFeatures .item.grid-3');
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
gsap.set(boxes, {
|
||||||
|
opacity: 0,
|
||||||
|
y: 30,
|
||||||
|
scale: 0.9
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加动画到时间线
|
||||||
|
boxTimeline.to(boxes, {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
scale: 1,
|
||||||
|
stagger: 0.15,
|
||||||
|
onComplete: () => {
|
||||||
|
gsap.set(boxes, { clearProps: "all" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优化heroTimeline动画效果
|
||||||
|
heroTimeline
|
||||||
|
.to(nameClip, {
|
||||||
|
transform: 'translate(0, 0)',
|
||||||
|
scale: 1,
|
||||||
|
duration: 1.0, // 缩短持续时间
|
||||||
|
ease: "elastic.out(1, 0.5)" // 改用弹性效果
|
||||||
|
})
|
||||||
|
.to([headingText, tagline], {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
stagger: 0.2, // 增加错开时间
|
||||||
|
duration: 0.8,
|
||||||
|
ease: "back.out(1.5)" // 添加弹跳效果
|
||||||
|
}, "-=0.3")
|
||||||
|
.to(actions, {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: "power2.out" // 更平滑的缓动
|
||||||
|
}, "-=0.2");
|
||||||
|
|
||||||
// 控制动画顺序
|
|
||||||
master.add(drawTimeline)
|
master.add(drawTimeline)
|
||||||
.add(reverseTimeline, "+=0.5")
|
.add(reverseTimeline, "+=0.3") // 缩短间隔时间
|
||||||
.add(wiggleTimeline) // 所有动画完成后开始微扰动画
|
.add(heroTimeline, "-=0.3")
|
||||||
|
.add(boxTimeline, "<+=1.3")
|
||||||
|
.add(wiggleTimeline, "<+=0.2");
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
background-color: var(--vp-c-brand-3);
|
background-color: var(--vp-c-brand-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.VPFeatures .item:hover .box .title,
|
||||||
.VPFeatures .item:hover .box .details {
|
.VPFeatures .item:hover .box .details {
|
||||||
color: var(--vp-c-white);
|
color: var(--vp-c-white);
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,52 @@ iframe {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPHero .name.clip {
|
.VPHero .heading .name.clip {
|
||||||
color: var(--vp-c-brand-2) !important;
|
background: var(--vp-home-hero-name-background);
|
||||||
background-color: var(--vp-c-brand-2) !important;
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: var(--vp-home-hero-name-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .heading .text {
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .tagline {
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .actions {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#home-0 {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-0 > span {
|
||||||
|
font-size: 20px;
|
||||||
|
opacity: 0.5;
|
||||||
|
line-height: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-1 {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-1 > span {
|
||||||
|
font-size: 20px;
|
||||||
|
opacity: 0.5;
|
||||||
|
line-height: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-2 {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-2 > span {
|
||||||
|
font-size: 20px;
|
||||||
|
opacity: 0.5;
|
||||||
|
line-height: 0.8;
|
||||||
}
|
}
|
31
.vscode/openmcp_connection.json
vendored
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"items": [
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"connectionType": "SSE",
|
|
||||||
"name": "SSE-1748530644202",
|
|
||||||
"version": "1.0",
|
|
||||||
"url": "http://localhost:8000/mcp",
|
|
||||||
"oauth": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"connectionType": "SSE",
|
|
||||||
"name": "SSE-1748531219080",
|
|
||||||
"version": "1.0",
|
|
||||||
"url": "http://localhost:8000/sse",
|
|
||||||
"oauth": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
}
|
|
215
.vscode/tabs.my-browser.json
vendored
@ -1,215 +0,0 @@
|
|||||||
{
|
|
||||||
"clientId": "81b3b1d6-b1d6-53b1d63c156-83b1d63c156a678-6a678e36",
|
|
||||||
"currentIndex": 0,
|
|
||||||
"tabs": [
|
|
||||||
{
|
|
||||||
"name": "交互测试",
|
|
||||||
"icon": "icon-robot",
|
|
||||||
"type": "blank",
|
|
||||||
"componentIndex": 3,
|
|
||||||
"storage": {
|
|
||||||
"activeNames": [
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "使用 ffmpeg 把视频 (0, 65) 到 (1173, 769) 的位置截取出来,视频的格式是 mov,我希望导出为 mp4",
|
|
||||||
"extraInfo": {
|
|
||||||
"created": 1749111492698,
|
|
||||||
"state": "success",
|
|
||||||
"serverName": "deepseek"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "你可以使用 `ffmpeg` 的 `crop` 滤镜来截取视频的指定区域,并将 `.mov` 转换为 `.mp4` 格式。以下是具体的命令:\n\n### 命令解析:\n1. **输入文件**:`input.mov`(替换为你的文件名)\n2. **裁剪参数**:\n - `crop=w:h:x:y`:从坐标 `(x,y)` 开始,截取宽度 `w`、高度 `h` 的区域。\n - 你的需求是从 `(0, 65)` 到 `(1173, 769)`,因此:\n - 宽度 `w = 1173 - 0 = 1173`\n - 高度 `h = 769 - 65 = 704`\n - 起始坐标 `(x,y) = (0, 65)`\n3. **输出格式**:`mp4`(默认使用 H.264 编码)\n\n### 完整命令:\n```bash\nffmpeg -i input.mov -filter:v \"crop=1173:704:0:65\" -c:a copy output.mp4\n```\n\n### 参数说明:\n- `-i input.mov`:指定输入文件。\n- `-filter:v \"crop=1173:704:0:65\"`:视频裁剪滤镜。\n- `-c:a copy`:直接复制音频流(无需重新编码,节省时间)。\n- `output.mp4`:输出文件名。\n\n### 其他选项:\n- 如果需要控制视频质量,可以添加 `-crf` 参数(默认 23,值越小质量越高):\n ```bash\n ffmpeg -i input.mov -filter:v \"crop=1173:704:0:65\" -crf 18 -c:a copy output.mp4\n ```\n- 如果需要硬件加速(如 NVIDIA GPU),可以添加 `-hwaccel cuda` 等参数。\n\n### 注意:\n1. 确保 `ffmpeg` 已安装并添加到系统路径。\n2. 如果视频有旋转/缩放等元数据,可能需要先处理(如用 `-vf \"transpose=1\"` 旋转)。\n\n运行命令后,`output.mp4` 就是你需要的裁剪后视频。",
|
|
||||||
"extraInfo": {
|
|
||||||
"created": 1749111520497,
|
|
||||||
"state": "success",
|
|
||||||
"serverName": "deepseek",
|
|
||||||
"usage": {
|
|
||||||
"prompt_tokens": 42,
|
|
||||||
"completion_tokens": 487,
|
|
||||||
"total_tokens": 529,
|
|
||||||
"prompt_tokens_details": {
|
|
||||||
"cached_tokens": 0
|
|
||||||
},
|
|
||||||
"prompt_cache_hit_tokens": 0,
|
|
||||||
"prompt_cache_miss_tokens": 42
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"modelIndex": 0,
|
|
||||||
"enableTools": [
|
|
||||||
{
|
|
||||||
"name": "k_navigate",
|
|
||||||
"description": "Navigate to a URL",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"url": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "URL to navigate to"
|
|
||||||
},
|
|
||||||
"launchOptions": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }"
|
|
||||||
},
|
|
||||||
"allowDangerous": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"url"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_screenshot",
|
|
||||||
"description": "Take a screenshot of the current page or a specific element",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Name for the screenshot"
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "CSS selector for element to screenshot"
|
|
||||||
},
|
|
||||||
"width": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Width in pixels (default: 800)"
|
|
||||||
},
|
|
||||||
"height": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Height in pixels (default: 600)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_click",
|
|
||||||
"description": "Click an element on the page",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"selector": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "CSS selector for element to click"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"selector"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_fill",
|
|
||||||
"description": "Fill out an input field",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"selector": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "CSS selector for input field"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Value to fill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"selector",
|
|
||||||
"value"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_select",
|
|
||||||
"description": "Select an element on the page with Select tag",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"selector": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "CSS selector for element to select"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Value to select"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"selector",
|
|
||||||
"value"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_hover",
|
|
||||||
"description": "Hover an element on the page",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"selector": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "CSS selector for element to hover"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"selector"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_evaluate",
|
|
||||||
"description": "Execute JavaScript in the browser console",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"script": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "JavaScript code to execute"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"script"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "k_get_full_page_text",
|
|
||||||
"description": "获取页面所有文本内容",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"enableWebSearch": false,
|
|
||||||
"temperature": 0.6,
|
|
||||||
"contextLength": 20,
|
|
||||||
"systemPrompt": "",
|
|
||||||
"parallelToolCalls": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
22
index.md
@ -40,6 +40,14 @@ features:
|
|||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id="home-0">
|
||||||
|
为您的 MCP Agent 开发排忧解难
|
||||||
|
<br>
|
||||||
|
<span>Providing Fun and Convenience for Your MCP Agent Development</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<BiliPlayer
|
<BiliPlayer
|
||||||
url="//player.bilibili.com/player.html?isOutside=true&aid=114445745397200&bvid=BV1zYGozgEHcautoplay=false"
|
url="//player.bilibili.com/player.html?isOutside=true&aid=114445745397200&bvid=BV1zYGozgEHcautoplay=false"
|
||||||
cover="https://picx.zhimg.com/80/v2-8c1f5d99066ed272554146ed8caf7cc3_1440w.png"
|
cover="https://picx.zhimg.com/80/v2-8c1f5d99066ed272554146ed8caf7cc3_1440w.png"
|
||||||
@ -47,7 +55,12 @@ features:
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## OpenMCP 为谁准备?
|
|
||||||
|
<h2 id="home-1">
|
||||||
|
OpenMCP 为谁准备?
|
||||||
|
<br>
|
||||||
|
<span>The Development of OpenMCP is for ...</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@ -88,8 +101,11 @@ features:
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
<h2 id="home-2">
|
||||||
## 问题解答 FAQ
|
问题解答
|
||||||
|
<br>
|
||||||
|
<span>Waiting for Your Questions</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<el-collapse>
|
<el-collapse>
|
||||||
<el-collapse-item title="OpenMCP 适合做什么?" name="1">
|
<el-collapse-item title="OpenMCP 适合做什么?" name="1">
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
layout: doc
|
||||||
|
---
|
||||||
|
|
||||||
|
# 常见问题解答
|
||||||
|
|
||||||
|
## 错误代码说明
|
||||||
|
|
||||||
|
### 32000 - MCP 连接失败
|
||||||
|
|
||||||
|
MCP 连接失败可能有多种原因,以下是一些常见情况:
|
||||||
|
|
||||||
|
• **虚拟环境路径不匹配**
|
||||||
|
|
||||||
|
虚拟环境(venv)与入口文件路径不匹配是导致连接失败的常见原因之一。
|
||||||
|
|
||||||
|
详细的解决方案请参考:[配置说明](./venv-not-same-path/venv-not-same-path.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
• **其他可能的原因**
|
||||||
|
|
||||||
|
- 端口被占用
|
||||||
|
- 环境变量配置错误
|
||||||
|
- 依赖库未正确安装
|
||||||
|
|
||||||
|
> 如果您遇到以上问题,请先查看错误日志获取详细信息。如果问题仍然存在,可以在 [GitHub Issues](https://github.com/LSTM-Kirigaya/openmcp-client/issues) 中寻求帮助。
|
BIN
plugin-tutorial/faq/venv-not-same-path/image-2.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
plugin-tutorial/faq/venv-not-same-path/image.png
Normal file
After Width: | Height: | Size: 21 KiB |
29
plugin-tutorial/faq/venv-not-same-path/venv-not-same-path.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 虚拟环境与入口文件不在同一目录时的配置方式
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
在使用 OpenMCP 时,有时会遇到虚拟环境(venv)与 Python 文件不在同一目录的情况,甚至虚拟环境可能位于项目文件夹之外。这种情况下,点击右上角连接按钮可能会出现 MCP 连接失败(错误代码:32000)的问题。
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 1. 调整执行目录
|
||||||
|
|
||||||
|
在连接选项中,您需要调整执行目录到虚拟环境所在的位置:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2. 修改执行命令
|
||||||
|
|
||||||
|
同时,需要相应地修改执行命令:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3. 直接指定解释器路径
|
||||||
|
|
||||||
|
对于特定情况,您可以直接在命令中指定 Python 解释器的完整路径,例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
C:\code\ygo-chat\.venv\Scripts\python.exe example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注意:此方法同样适用于 node或者mcp指令的【命令】以及其它mcp client的mcp配置文件。
|
@ -1 +1,69 @@
|
|||||||
# 连接大模型
|
# 连接大模型
|
||||||
|
|
||||||
|
如果需要使用「交互测试」来在和大模型的交互中测试 MCP 工具的性能,你需要首先需要在 OpenMCP 配置大模型。
|
||||||
|
|
||||||
|
:::warning 协议兼容性警告
|
||||||
|
目前 OpenMCP 只支持符合 OpenAI 接口规范的 大模型服务,其他大模型的调用需要请通过 [newApi](https://github.com/QuantumNous/new-api) 进行转发或者自行实现。
|
||||||
|
|
||||||
|
目前市面上主流的如下模型我们都是支持的,如果遇到大模型连接的问题,请随时 [[channel|联系我们]]。
|
||||||
|
:::
|
||||||
|
|
||||||
|
在 「设置」-「API」 可以进入大模型的连接配置界面。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 默认支持的模型
|
||||||
|
|
||||||
|
OpenMCP 默认填充了市面上常见的大模型,下面是支持的模型
|
||||||
|
|
||||||
|
| 大模型 Name | 提供商 | baseUrl | 默认模型 |
|
||||||
|
|----------------------|---------------------------|---------------------------------------------|-----------------------|
|
||||||
|
| DeepSeek | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
|
||||||
|
| OpenAI | OpenAI | `https://api.openai.com/v1` | `gpt-4-turbo` |
|
||||||
|
| 通义千问 Qwen | Alibaba | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` |
|
||||||
|
| 豆包 Seed | ByteDance | `https://ark.cn-beijing.volces.com/api/v3` | `doubao-1.5-pro-32k` |
|
||||||
|
| Gemini | Google | `https://generativelanguage.googleapis.com/v1beta/openai/` | `gemini-2.0-flash` |
|
||||||
|
| Grok | xAI | `https://api.x.ai/v1` | `grok-3-mini` |
|
||||||
|
| Mistral | Mistral AI | `https://api.mistral.ai/v1` | `mistral-tiny` |
|
||||||
|
| Groq | Groq | `https://api.groq.com/openai/v1` | `mixtral-8x7b-32768` |
|
||||||
|
| Perplexity | Perplexity AI | `https://api.perplexity.ai/v1` | `pplx-7b-online` |
|
||||||
|
| Kimi Chat | 月之暗面 (Moonshot AI) | `https://api.moonshot.cn/v1` | `moonshot-v1-8k` |
|
||||||
|
|
||||||
|
|
||||||
|
## 配置大模型
|
||||||
|
|
||||||
|
你需要做的只是把对应服务商的 apiToken 填入 openmcp 中即可。然后点击「测试」,看到下面的响应说明连接成功。您就可以在交互测试里面使用大模型了!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
:::warning
|
||||||
|
有些用户会遇到无法访问的问题,请确保你的 baseUrl 填写正确。如果在国内使用某些国外厂商的服务,比如 gemini,openai,请确保你的网络环境可以访问到这些服务。在 「设置」-「通用」中你可以设置代理服务器。
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 添加模型
|
||||||
|
|
||||||
|
如果你想使用的指定服务商的模型不在默认支持的模型中,有两种方法可以添加它们。
|
||||||
|
|
||||||
|
### 方法一:更新模型列表
|
||||||
|
|
||||||
|
此处以通义千问为例子,确保在 apitoken 填写正确的情况下,点击「更新模型列表」,如果服务商严格实现了 openai 标准,那么就能看到所有更新的模型了。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 方法二:手动添加模型
|
||||||
|
|
||||||
|
如果你的服务器没有支持 openai 标准,你将无法使用「方法一」,你可以如此手动添加模型列表。此处以 Grok 为例,在服务商中找到 grok,点击图中所示的编辑
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击模型,输入模型名称,回车,点击确认:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
回到 api 页面,再点击保存。
|
||||||
|
|
||||||
|
## 添加服务
|
||||||
|
|
||||||
|
如果你要的服务商没有出现我们的列表中(云服务商的服务,或者自己部署的服务),可以通过「添加服务」按钮来添加自定义模型,使用方法和「添加模型」「方法二:手动添加模型」类似,就不赘述了。
|
||||||
|
@ -66,10 +66,10 @@ MCP 客户端是指能够通过 MCP 协议进行通信的大模型对话客户
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
当前工作区曾经连接过的 mcp 服务器会出现在这里,这是因为 openmcp 默认将工作区启动的 mcp 的连接信息存储在了 `.vscode/tabs.{server-name}.json` 中,其中 `{server-name}` 就是 mcp 服务器连接成功的服务器名称。
|
当前工作区曾经连接过的 mcp 服务器会出现在这里,这是因为 openmcp 默认将工作区启动的 mcp 的连接信息存储在了 `.openmcp/tabs.{server-name}.json` 中,其中 `{server-name}` 就是 mcp 服务器连接成功的服务器名称。
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
注意,同一个项目中,你不应该有两个名字完全相同的 mcp 服务器,这会导致 `.vscode/tabs.{server-name}.json` 连接信息存储冲突,发生未知错误。
|
注意,同一个项目中,你不应该有两个名字完全相同的 mcp 服务器,这会导致 `.openmcp/tabs.{server-name}.json` 连接信息存储冲突,发生未知错误。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
如果你想要在任意工作区都能使用同一个 mcp 服务器,可以考虑在「安装的 MCP 服务器」中添加成熟耐用的 mcp 服务器,这个位置添加的 mcp 服务器全局可用。
|
如果你想要在任意工作区都能使用同一个 mcp 服务器,可以考虑在「安装的 MCP 服务器」中添加成熟耐用的 mcp 服务器,这个位置添加的 mcp 服务器全局可用。
|
||||||
|
@ -11,10 +11,10 @@ openmcp 具备自动保存测试结果的功能。如下的行为会触发 openm
|
|||||||
- 创建标签页,并选择一个有效的调试项目
|
- 创建标签页,并选择一个有效的调试项目
|
||||||
- 在调试页进行调试行为(选择工具,执行工具,询问大模型等)
|
- 在调试页进行调试行为(选择工具,执行工具,询问大模型等)
|
||||||
|
|
||||||
当前 mcp 项目的测试数据会被保存在 `.vscode/tabs.{server-name}.json` 中,其中 `{server-name}` 就是 mcp 服务器连接成功的服务器名称。
|
当前 mcp 项目的测试数据会被保存在 `.openmcp/tabs.{server-name}.json` 中,其中 `{server-name}` 就是 mcp 服务器连接成功的服务器名称。
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
注意,同一个项目中,你不应该有两个名字完全相同的 mcp 服务器,这会导致 `.vscode/tabs.{server-name}.json` 连接信息存储冲突,发生未知错误。
|
注意,同一个项目中,你不应该有两个名字完全相同的 mcp 服务器,这会导致 `.openmcp/tabs.{server-name}.json` 连接信息存储冲突,发生未知错误。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 快速调试
|
## 快速调试
|
||||||
|
@ -1 +1,44 @@
|
|||||||
# 分发您的实验结果
|
# 分发您的实验结果
|
||||||
|
|
||||||
|
## 标签页恢复
|
||||||
|
|
||||||
|
openmcp 默认会实时保存您的实验结果,每一个在工作区开启的服务器默认会将结果存储在 `.openmcp/tabs.{server-name}.json` 中,其中 `{server-name}` 就是 mcp 服务器连接成功的服务器名称。
|
||||||
|
|
||||||
|
请确保您的 `.gitignore` 文件中没有包含匹配到 .openmcp 文件夹的规则。这样,当您通过 git 提交你的代码,对 agent 的代码进行管理时,当你在别的电脑上 clone 或者他人 clone 你的项目时,就能快速恢复你上一次的实验内容,继续进行实验或者开发调试。
|
||||||
|
|
||||||
|
## 连接恢复
|
||||||
|
|
||||||
|
每一个 mcp 服务器的连接信息会被保存在 `.openmcp/connection.json` 中,下面是一个例子:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"connectionType": "STDIO",
|
||||||
|
"command": "mcp",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"main.py"
|
||||||
|
],
|
||||||
|
"url": "",
|
||||||
|
"cwd": "{workspace}/simple-mcp",
|
||||||
|
"oauth": "",
|
||||||
|
"clientName": "openmcp.connect.STDIO",
|
||||||
|
"clientVersion": "0.0.1",
|
||||||
|
"env": {},
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "锦恢的 MCP Server",
|
||||||
|
"version": "1.9.2"
|
||||||
|
},
|
||||||
|
"filePath": "{workspace}/simple-mcp/main.py",
|
||||||
|
"name": "锦恢的 MCP Server",
|
||||||
|
"version": "1.9.2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
当您打开左侧的控制面板或者打开一个过去打开过的 mcp 服务器时, mcp 默认会根据上面的信息来获取工作区的服务器列表或者尝试进行自动连接。如果 openmcp 在连接 mcp 时发生了初始化错误或者保存错误,除了向 openmcp 官方求助外,您还可以尝试手动管理 `.openmcp/connection.json` 文件。
|
BIN
plugin-tutorial/usage/images/add-new-mcp.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
plugin-tutorial/usage/images/drag-to-fill.png
Normal file
After Width: | Height: | Size: 411 KiB |
BIN
plugin-tutorial/usage/images/parallel-tool-call.png
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
plugin-tutorial/usage/images/prompt.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
plugin-tutorial/usage/images/resource.png
Normal file
After Width: | Height: | Size: 591 KiB |
BIN
plugin-tutorial/usage/images/setting-api-edit-1.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
plugin-tutorial/usage/images/setting-api-edit.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
plugin-tutorial/usage/images/setting-api-test.png
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
plugin-tutorial/usage/images/setting-api.png
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
plugin-tutorial/usage/images/setting-update-models.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
plugin-tutorial/usage/images/system-prompt.png
Normal file
After Width: | Height: | Size: 326 KiB |
@ -1 +1,35 @@
|
|||||||
# 连接多个 MCP 服务器
|
# 连接多个 MCP 服务器
|
||||||
|
|
||||||
|
openmcp 支持连接多个 MCP 服务器。
|
||||||
|
|
||||||
|
假设你现在想要实现一个可以自动查阅资料并且整理成 word 文档的 agent,你可以这样做:
|
||||||
|
|
||||||
|
1. 找到能进行网络搜索的 mcp:[crawl4ai mcp](https://github.com/LSTM-Kirigaya/openmcp-tutorial/tree/main/crawl4ai-mcp)
|
||||||
|
2. 找到能进行 word 操作的 mcp:[Office-Word-MCP-Server](https://github.com/GongRzhe/Office-Word-MCP-Server)
|
||||||
|
3. 在 openmcp 中把它们组合起来。
|
||||||
|
4. 自动完成你的任务!
|
||||||
|
|
||||||
|
假设,我们已经连接了第一个 mcp,也就是 crawl4ai mcp,我们可以添加额外的 mcp 服务器:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 添加方法一:拖拽
|
||||||
|
|
||||||
|
直接把需要加入的 mcp 服务器的文件,按住 shift 点击拖拽进入 openmcp 界面就能自动完成参数的填充。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
:::warning
|
||||||
|
自动填充的命令不一定总是准确的,在 [STDIO 连接的启动](http://localhost:5173/openmcp/plugin-tutorial/usage/connect-mcp.html#stdio-%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%90%AF%E5%8A%A8) 中我们说过这一点。具体的连接方法请阅读 [附录:关于 uv 启动 mcp 你必须知道的](http://localhost:5173/openmcp/plugin-tutorial/quick-start/first-mcp.html#%E9%99%84%E5%BD%95-%E5%85%B3%E4%BA%8E-uv-%E5%90%AF%E5%8A%A8-mcp-%E4%BD%A0%E5%BF%85%E9%A1%BB%E7%9F%A5%E9%81%93%E7%9A%84) 后自行判断。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 添加方法二:手动填写参数
|
||||||
|
|
||||||
|
手动填写参数,没啥好说的。
|
||||||
|
|
||||||
|
|
||||||
|
## 使用多服务器
|
||||||
|
|
||||||
|
多服务器连接后的使用和单服务器没有太大的区别,openmcp 内部会自动完成工具的调度和选择。唯一需要注意的是,多服务器的 tool name 一定不要重名,否则会出现冲突。
|
||||||
|
|
||||||
|
如果您认为 tool 重名有存在的必要性,请通过 [issue](https://github.com/LSTM-Kirigaya/openmcp-client/issues) 让我们知道您的场景和想法,根据讨论,我们会支持。
|
@ -1 +1,72 @@
|
|||||||
# 用大模型测试您的 mcp
|
# 用大模型测试您的 mcp
|
||||||
|
|
||||||
|
如果您完成了 [[connect-llm|连接 mcp 服务器]] 这一步,那么您就可以开始测试您的 mcp 了。
|
||||||
|
|
||||||
|
在 [[put-into-llm|扔进大模型里面测测好坏!]] 中,我们已经通过一个简单的例子来展示来如何使用大模型测试您的 mcp。因此,这篇文章更多是讲解不便在「快速开始」中赘述的细节。
|
||||||
|
|
||||||
|
|
||||||
|
和大模型交互时,有一些参数可以选择,也就是输入框下面那一排按钮,我来简单介绍一下。
|
||||||
|
|
||||||
|
## 选择模型
|
||||||
|
|
||||||
|
顾名思义,你可以在这里切换你的模型。值得一提的是,openmcp 会以单条对话为粒度来记录每一条对话使用的模型。您可以利用这一特性来进行混合模型测试。
|
||||||
|
|
||||||
|
如果你没有找到你想要的模型,或是想要添加额外的模型,请移步 [[connect-llm|连接 mcp 服务器]] 来了解如何添加模型。
|
||||||
|
|
||||||
|
## 系统提示词
|
||||||
|
|
||||||
|
您可以在这里选择和添加系统提示词。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
openmcp 默认将您的系统提示词保存在 `~/.openmcp/nedb/systemPrompt.db` 中。您可以通过 nedb 来进行反序列化和拷贝。
|
||||||
|
|
||||||
|
|
||||||
|
## 提词
|
||||||
|
|
||||||
|
您可以利用该模块来调用 mcp 服务器提供的 prompt 功能,生成的 prompt 字段会作为富文本被插入您的对话中。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 资源
|
||||||
|
|
||||||
|
您可以利用该模块来调用 mcp 服务器提供的 resource 功能,生成的 resource 字段会作为富文本被插入您的对话中。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
:::warning openmcp 不负责 resource 的数据持久化!
|
||||||
|
请注意!每次对话完成后 resource 是否会被保存到磁盘完全由 mcp server 作者决定,openmcp 不负责 resource 的数据持久化!如果您发现关闭 openmcp 再打开,resource 彩蛋为空,这不是 openmcp 的 bug,而是 mcp server 作者没有支持数据持久化!
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 允许模型在单轮回复中调用多个工具
|
||||||
|
|
||||||
|
大模型在进行工具调用时,有时候会将在一次回复中要求调用多次工具,比如你想要同时获取三个网页的内容翻译,大模型可能会同时调用三次「网络搜索」工具(如果你提供了的话)。多次工具使用时,openmcp 会如此渲染调用执行过程:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
openmcp 输入框的按钮中的「允许模型在单轮回复中调用多个工具」默认是点亮的。也就是允许大模型可以在一次回复中调用多次工具。
|
||||||
|
|
||||||
|
有的时候,我们希望命令一条条执行,就可以选择把这个按钮熄灭。
|
||||||
|
|
||||||
|
:::warning 协议兼容性警告
|
||||||
|
有的厂商(比如 gemini)不一定严格支持了 openai 协议,对于不支持 「允许模型在单轮回复中调用多个工具」的厂商,openmcp 的后端会自动将该选项关闭。
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 温度参数
|
||||||
|
|
||||||
|
温度参数越高,生成内容的随机性越强,对于通用大模型,个人建议参数为 0.6 ~ 0.7,会是一个适用于通用型任务的数值。OpenMCP 提供的默认值为 0.6。
|
||||||
|
|
||||||
|
## 上下文长度
|
||||||
|
|
||||||
|
上下文长度代表了大模型的最大上下文轮数,默认值为 20。举个例子,如果你和大模型对话产生了 40 轮数据(工具调用条数+你的问题数量+大模型回答的次数总共 40 个),那么下一次对话 openmcp 只会发送最后 20 条数据给大模型。
|
||||||
|
|
||||||
|
:::warning 上下文长度不要太小!
|
||||||
|
我们强烈不推荐你将这个数值设置低于 20,因为大模型接受工具调用的结果需要和之前的调用请求一一对应。如果不对应,大模型会返回注入 400 这样的错误。遇到这样的错误,请从最初一句话重启或者新开一个「交互测试」。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## MCP 服务器超时
|
||||||
|
|
||||||
|
MCP 服务器默认超时时间为 30 秒,如果您的 mcp 服务器需要更长的时间才能完成任务,您可以通过「设置」「通用」来设置超时时间,单位为秒,该设置全局生效。
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
// 要排除的文件夹
|
// 要排除的文件夹
|
||||||
const excludedDirs = ['node_modules', '.vitepress', '.vscode', 'scripts'];
|
const excludedDirs = ['node_modules', '.vitepress', '.openmcp', 'scripts'];
|
||||||
|
|
||||||
// 递归搜索图片文件夹
|
// 递归搜索图片文件夹
|
||||||
function findImageDirs(rootDir, currentDir = '', results = []) {
|
function findImageDirs(rootDir, currentDir = '', results = []) {
|
||||||
|