fix(site): add MCP tools for query examples and jump examples (#3623)
This commit is contained in:
parent
41b9fbaade
commit
96cd780f26
|
@ -10,7 +10,7 @@
|
||||||
<div class="right-panel" :class="{ collapsed: !showTinyRobot }">
|
<div class="right-panel" :class="{ collapsed: !showTinyRobot }">
|
||||||
<tiny-robot-chat />
|
<tiny-robot-chat />
|
||||||
</div>
|
</div>
|
||||||
<IconAi @click="handleShowTinyRobot" class="style-settings-icon"></IconAi>
|
<IconAi v-show="!showTinyRobot" @click="handleShowTinyRobot" class="style-settings-icon"></IconAi>
|
||||||
<tiny-dialog-box
|
<tiny-dialog-box
|
||||||
v-model:visible="boxVisibility"
|
v-model:visible="boxVisibility"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
|
@ -136,7 +136,7 @@ const handleShowTinyRobot = () => {
|
||||||
padding: 34px 0 0;
|
padding: 34px 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right-panel {
|
.right-panel:not(.collapsed) {
|
||||||
:deep(.tr-container) {
|
:deep(.tr-container) {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<tr-container v-model:show="showTinyRobot" v-model:fullscreen="fullscreen">
|
<tr-container v-model:show="showTinyRobot" v-model:fullscreen="fullscreen">
|
||||||
<tr-bubble-provider :message-renderers="messageRenderers">
|
<tr-bubble-provider :message-renderers="messageRenderers">
|
||||||
<div v-if="showMessages.length === 0">
|
<div class="robot-top-msg" v-if="showMessages.length === 0">
|
||||||
<tr-welcome title="智能助手" description="您好,我是Opentiny AI智能助手" :icon="welcomeIcon">
|
<tr-welcome title="智能助手" description="您好,我是Opentiny AI智能助手" :icon="welcomeIcon">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="welcome-footer"></div>
|
<div class="welcome-footer"></div>
|
||||||
|
@ -17,7 +17,8 @@
|
||||||
@item-click="handlePromptItemClick"
|
@item-click="handlePromptItemClick"
|
||||||
></tr-prompts>
|
></tr-prompts>
|
||||||
</div>
|
</div>
|
||||||
<tr-bubble-list v-else :items="showMessages" :roles="roles" auto-scroll> </tr-bubble-list>
|
<tr-bubble-list v-else class="robot-top-msg markdown-body" :items="showMessages" :roles="roles" auto-scroll>
|
||||||
|
</tr-bubble-list>
|
||||||
</tr-bubble-provider>
|
</tr-bubble-provider>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
@ -172,6 +173,11 @@ watch(() => messages.value[messages.value.length - 1]?.content, scrollToBottom)
|
||||||
height: 600px;
|
height: 600px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 聊天顶部要撑开空间 */
|
||||||
|
.robot-top-msg {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -22,8 +22,13 @@ export const useTinyRobot = () => {
|
||||||
|
|
||||||
const promptItems = [
|
const promptItems = [
|
||||||
{
|
{
|
||||||
label: '智能操作网页',
|
label: '快速跳到文档',
|
||||||
description: '帮我选中最贵的手机商品',
|
description: '帮我切换到国际化指南',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '快速跳到组件',
|
||||||
|
description: '帮我切换到 Select 组件',
|
||||||
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -54,14 +59,24 @@ export const useTinyRobot = () => {
|
||||||
const suggestionPillItems = [
|
const suggestionPillItems = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
text: '帮我选中最贵的手机商品',
|
text: 'Select',
|
||||||
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: '表格',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: '树组件',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
function handleSuggestionPillItemClick(item: SuggestionItem) {
|
function handleSuggestionPillItemClick(item: SuggestionItem) {
|
||||||
if (item.id === '1') {
|
if (item.id === '1') {
|
||||||
let templateText = `请对 [目标组件] ,执行 [操作]`
|
let templateText = `帮我跳转到 [目标组件]`
|
||||||
let currentInitialValue = { 目标组件: item.text, 操作: '' }
|
let currentInitialValue = { 目标组件: item.text, 操作: '' }
|
||||||
|
|
||||||
if (senderRef.value) {
|
if (senderRef.value) {
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { genMenus } from '../menus'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 组件页面的右上导航的数据回调函数
|
||||||
|
export const cmpAnchorDataCallback = { value: null }
|
||||||
|
|
||||||
export const createGlobalMcpTool = (server) => {
|
export const createGlobalMcpTool = (server) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
server.registerResource(
|
server.registerResource(
|
||||||
|
@ -26,11 +29,13 @@ export const createGlobalMcpTool = (server) => {
|
||||||
'swtich-router',
|
'swtich-router',
|
||||||
{
|
{
|
||||||
title: 'router',
|
title: 'router',
|
||||||
description: '可以帮用户跳转页面,比如:跳转到组件文档页面和API文档页面',
|
description: '可以帮用户跳转到文档页面,组件示例的总页面或组件API文档页面,或组件库的概览页面',
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
key: z.string().describe('跳转页面路径'),
|
key: z.string().describe('跳转页面路径'),
|
||||||
type: z.enum(['components', 'docs', 'overview', 'features']).describe('跳转页面类型'),
|
type: z
|
||||||
isOpenApi: z.boolean().describe('是否打开API文档')
|
.enum(['components', 'docs', 'overview', 'features'])
|
||||||
|
.describe('跳转页面类型,比如:组件的页面,文档的页面,组件的概览页面'),
|
||||||
|
isOpenApi: z.boolean().describe('跳转到组件页面时,是否打开API文档')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async ({ key, type, isOpenApi }) => {
|
async ({ key, type, isOpenApi }) => {
|
||||||
|
@ -51,6 +56,49 @@ export const createGlobalMcpTool = (server) => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
'get-component-demos',
|
||||||
|
{
|
||||||
|
title: '查询全部示例的信息',
|
||||||
|
description:
|
||||||
|
'查询当前组件的全部示例信息,demos信息。返回值是一个数组,其中每一项的 demoId 属性是示例的键,通过键可以跳转到该示例。desc属性是示例的详细描述。',
|
||||||
|
inputSchema: {}
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// 通知组件页面返回右侧导航的数据
|
||||||
|
if (cmpAnchorDataCallback.value != null) {
|
||||||
|
const links = cmpAnchorDataCallback.value()
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: JSON.stringify(links) }]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: '找不到示例' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
'jump-to-demo',
|
||||||
|
{
|
||||||
|
title: '跳转到组件的示例demo',
|
||||||
|
description: '根据参数demoId, 跳转到指定的示例demo。',
|
||||||
|
inputSchema: {
|
||||||
|
demoId: z.string().describe('示例的id,唯一标识。')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ demoId }) => {
|
||||||
|
// 通知组件页面返回右侧导航的数据
|
||||||
|
location.hash = '#' + demoId
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: '跳转示例成功' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 长任务示例
|
// 长任务示例
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
'long-task',
|
'long-task',
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, computed, watch, onMounted, nextTick, ref } from 'vue'
|
import { reactive, computed, watch, onMounted, nextTick, ref, onUnmounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { TinyTabs, TinyTabItem } from '@opentiny/vue'
|
import { TinyTabs, TinyTabItem } from '@opentiny/vue'
|
||||||
import { debounce } from '@opentiny/utils'
|
import { debounce } from '@opentiny/utils'
|
||||||
|
@ -106,6 +106,7 @@ import ApiDocs from '../../components/api-docs.vue'
|
||||||
import McpDocs from '../../components/mcp-docs.vue'
|
import McpDocs from '../../components/mcp-docs.vue'
|
||||||
import useTasksFinish from '../../composable/useTasksFinish'
|
import useTasksFinish from '../../composable/useTasksFinish'
|
||||||
import { appData } from '../../tools/appData'
|
import { appData } from '../../tools/appData'
|
||||||
|
import { cmpAnchorDataCallback } from '../../tools/globalMcpTool'
|
||||||
|
|
||||||
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
|
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
|
||||||
|
|
||||||
|
@ -442,6 +443,11 @@ const handleAnchorClick = (e, data) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmpAnchorDataCallback.value = () => state.currJson.demos
|
||||||
|
onUnmounted(() => {
|
||||||
|
cmpAnchorDataCallback.value = null
|
||||||
|
})
|
||||||
|
|
||||||
defineExpose({ loadPage })
|
defineExpose({ loadPage })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue