This commit is contained in:
锦恢 2025-05-27 04:21:40 +08:00
parent 160ca6dfd1
commit a6115d0733
10 changed files with 362 additions and 16 deletions

View File

@ -24,7 +24,7 @@ export default defineConfig({
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
{ icon: 'github', link: 'https://github.com/LSTM-Kirigaya/openmcp-client' }
],
footer: {
message: '缩短LLM到Agent的最后一公里',

View File

@ -0,0 +1,193 @@
<template>
<div class="k-tabs" :style="panelStyle">
<div class="k-tabs-tags">
<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)"
:class="{ 'active-tab': tabsContainer.lastPaneId === pane.id }">
<span :class="pane.labelClass"></span>
<span>{{ pane.label }}</span>
</div>
</div>
<div class="k-tabs-content" :ref="el => tabsContainer.panelContainer = el">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, useSlots, provide, nextTick, computed, onMounted } from 'vue';
type PaneInfo = {
id: number;
label: 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();
let maxChildHeight = 0;
function resizeTab(id: number) {
const container = tabsContainer.panelContainer;
if (container) {
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,
activeLabel: '',
hoverBar: null,
panelContainer: undefined,
height: '0',
getPanes(el: HTMLElement | null, id: string | number) {
if (el) {
this.panes[id] = el;
}
},
switchLabel(id: number) {
if (this.lastPaneId === id) {
return;
}
this.lastPaneId = id;
this.activeLabel = this.paneInfos[id]?.label || '';
const container = tabsContainer.panelContainer;
const panels = Array.from(container.children) as HTMLElement[];
panels.forEach((panel, index) => {
console.log('index', index);
console.log('id', id);
panel.style.transition = 'opacity 0.3s ease';
if (index === id) {
panel.style.display = 'block';
setTimeout(() => {
panel.style.opacity = '1';
}, 150);
} else {
panel.style.opacity = '0';
setTimeout(() => {
panel.style.display = 'none';
}, 300);
}
});
nextTick(() => {
resizeTab(id);
});
},
updateLabels() {
const defaultChildren = slots.default?.() || [];
this.paneInfos = [];
for (const index in defaultChildren) {
const vnode = defaultChildren[index];
this.paneInfos.push({
id: Number(index),
label: vnode.props?.label || '',
labelClass: vnode.props?.labelClass || '',
});
}
if (this.paneInfos.length > 0) {
this.activeLabel = this.paneInfos[0]?.label || '';
nextTick(() => {
resizeTab(0);
});
}
}
});
tabsContainer.updateLabels();
onMounted(() => {
if (tabsContainer.panelContainer) {
const panels = Array.from(tabsContainer.panelContainer.children) as HTMLElement[];
panels.forEach((panel, index) => {
panel.style.position = 'absolute';
if (index != tabsContainer.lastPaneId) {
panel.style.display = 'none';
panel.style.opacity = '0';
}
});
}
});
</script>
<style scoped>
.k-tabs {
position: relative;
height: fit-content;
}
.k-tabs-tags {
display: flex;
margin-bottom: 20px;
width: 100%;
position: relative;
}
.k-tabs-tag-item {
background-color: var(--vp-button-alt-bg);
color: white;
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;
}
.k-tabs-tag-item:hover {
background-color: var(--vp-c-brand-2);
}
.k-tabs-tag-item.active-tab {
background-color: var(--vp-c-brand-3);
}
@media screen and (max-width: 414px) {
.k-tabs-tags {
overflow-x: scroll
}
}
.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>* {
transition: opacity 0.3s ease;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div class="wrapper">
<div class="bilibili-player-container">
<iframe v-if="isPlaying" :src="playerUrl" frameborder="0" allowfullscreen></iframe>
<div v-else class="cover-container" @click="playVideo">
<img :src="props.cover" class="cover-image" />
<button class="play-button">
<svg viewBox="0 0 24 24" width="48" height="48">
<path fill="currentColor" d="M8 5v14l11-7z" />
</svg>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
url: {
type: String,
required: true
},
cover: {
type: String,
required: true
}
})
const isPlaying = ref(false);
const playerUrl = ref(props.url);
function playVideo() {
isPlaying.value = true
}
</script>
<style scoped>
.wrapper {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.bilibili-player-container {
position: relative;
width: 72.36vw;
height: 38.26vw;
aspect-ratio: 16/9;
border-radius: .5em;
overflow: hidden;
border: 2px solid var(--vp-c-brand-3);
}
.cover-container {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
}
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 64px;
height: 64px;
border-radius: 50%;
background-color: var(--vp-c-brand-3);
border: none;
color: var(--vp-c-bg);
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.2s ease, background-color 0.2s ease;
}
.play-button:hover {
transform: translate(-50%, -50%) scale(1.1);
background-color: var(--vp-c-brand-2);
}
.play-button svg {
margin-left: 4px;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
</style>

View File

@ -29,8 +29,8 @@
position: absolute;
top: 50%;
left: 50%;
max-width: 500px;
max-height: 500px;
max-width: 380px;
max-height: 380px;
height: 99%;
object-fit: contain;
transform: translate(-50%, -50%);

View File

@ -29,7 +29,15 @@ const props = defineProps({
image: {
type: String,
required: true
}
},
label: {
type: String,
required: false
},
labelClass: {
type: String,
required: false
},
});
</script>

View File

@ -4,6 +4,8 @@ import type { Theme } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import HeroImage from './components/home/HeroImage.vue';
import TwoSideLayout from './components/home/TwoSideLayout.vue';
import KTab from './components/KTab/index.vue';
import BiliPlayer from './components/bilibli-player/index.vue';
import './style.css';
@ -16,5 +18,7 @@ export default {
},
enhanceApp({ app, router, siteData }) {
app.component('TwoSideLayout', TwoSideLayout);
app.component('KTab', KTab);
app.component('BiliPlayer', BiliPlayer);
}
} satisfies Theme

View File

@ -140,3 +140,14 @@
.two-side-layout .image-container {
display: unset !important;
}
.VPHero .image-container img {
box-shadow: unset !important;
}
iframe {
border: 2px solid var(--vp-c-brand-3);
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: border-color 0.3s ease;
}

BIN
images/openmcp-default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/opensource.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -17,32 +17,59 @@ hero:
- theme: alt
text: GitHub
link: https://github.com/LSTM-Kirigaya/openmcp-client
features:
- title: 集成调试环境
details: 将检查器与 MCP 客户端功能相结合,实现无缝开发和测试
- title: 全面的项目管理
details: 提供完整的项目级控制面板,实现高效的 MCP 项目监督
- title: 多模型支持
details: 支持多种大语言模型,具备灵活的集成能力
---
<BiliPlayer
url="//player.bilibili.com/player.html?isOutside=true&aid=114445745397200&bvid=BV1zYGozgEHcautoplay=false"
cover="https://picx.zhimg.com/80/v2-8c1f5d99066ed272554146ed8caf7cc3_1440w.png"
/>
<br>
<br>
<br>
<hr>
<br>
<br>
# OpenMCP 为谁准备?
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 2px 8px; font-size: 0.8rem; background-color: var(--vp-c-brand-3); color: white; border-radius: .5em; text-decoration: none; margin-right: 10px;">开源社区爱好者</a><a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 2px 8px; font-size: 0.8rem; background-color: var(--vp-button-alt-bg); color: white; border-radius: .5em; text-decoration: none; margin-right: 10px;">专业软件工程师</a><a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 2px 8px; font-size: 0.8rem; background-color: var(--vp-button-alt-bg); color: white; border-radius: .5em; text-decoration: none; margin-right: 10px;">AI研发科学家</a>
<br>
<KTab>
<TwoSideLayout
label="专业软件工程师"
:texts="[
'在编辑器中写完代码直接测试,无需打开第三方软件。提供极其丰富的功能和特性。',
'测试左移,让你的开发与测试一体化,无需打开第三方软件。提供极其丰富的功能和特性。',
'在左侧面板自由而优雅地管理、调试和测试你的智能体。',
'大模型调用工具的每一个细节一览无余,不满意的调用结果直接一键复现。',
'每一次对话都会显示各项性能指标,方便进行成本管理。',
'系统提示词管理面板,让您轻松用 mcp 服务器和系统提示词构建您的智能体应用。',
'每一次测试的细节都会 100% 跟随 git 进行版本控制,方便你分享你的每一次试验结果,也方便你零成本复现别人的 mcp 项目。'
]"
image="/images/openmcp.chatbot.png"
/>
<TwoSideLayout
label="开源社区爱好者"
:texts="[
'测试左移,让你的开发与测试一体化,无需打开第三方软件。提供极其丰富的功能和特性。',
'OpenMCP 完全开源,您不仅可以免费试用此产品,也可以一起加入我们,实现你的关于 Agent 的奇思妙想',
'完全公开技术细节您不必担心您的创意和token会遭到剽窃',
'可持久化的系统提示词管理面板,让您可以将实际的 mcp 服务器的系统提示词进行测试,以便于在社区内进行分享',
'每一次测试的细节都会 100% 跟随 git 进行版本控制,方便你分享你的每一次试验结果,也方便你零成本复现别人的 mcp 项目。'
]"
image="/images/opensource.png"
/>
<TwoSideLayout
label="AI研发科学家"
:texts="[
'测试左移,让你的开发与测试一体化,无需打开第三方软件。提供极其丰富的功能和特性。',
'只需几行代码,就能快速将您的科研成果以做成 mcp 服务器,从而接入任意大模型,以实现用户友好型的交互界面。',
'所有实验数据与配置参数均自动纳入Git版本管理系统确保研究成果可追溯、可复现便于学术交流与论文复现。',
'基于 OpenMCP 快速完成您的 demo缩短创新到落地的距离',
]"
image="/images/openmcp.chatbot.png"
/>
</KTab>