Skip to content

Commit c7ee104

Browse files
committed
feat: add mcp
1 parent 66427c6 commit c7ee104

10 files changed

Lines changed: 660 additions & 28 deletions

File tree

packages/plugins/robot/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
"homepage": "https://opentiny.design/tiny-engine",
2727
"dependencies": {
2828
"@opentiny/tiny-engine-meta-register": "workspace:*",
29-
"@opentiny/tiny-robot": "0.2.0-alpha.1",
30-
"@opentiny/tiny-robot-kit": "0.2.0-alpha.1",
31-
"@opentiny/tiny-robot-svgs": "0.2.0-alpha.1"
29+
"@opentiny/tiny-robot": "0.3.0-alpha.8",
30+
"@opentiny/tiny-robot-kit": "0.3.0-alpha.8",
31+
"@opentiny/tiny-robot-svgs": "0.3.0-alpha.8",
32+
"dompurify": "^3.0.1",
33+
"markdown-it": "^14.1.0"
3234
},
3335
"devDependencies": {
3436
"@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*",
37+
"@types/markdown-it": "^14.1.2",
3538
"@vitejs/plugin-vue": "^5.1.2",
3639
"@vitejs/plugin-vue-jsx": "^4.0.1",
3740
"vite": "^5.4.2"

packages/plugins/robot/src/Main.vue

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<icon-new-session />
3737
</button>
3838
</template>
39-
<template v-if="activeMessages.length === 0">
39+
<div v-if="activeMessages.length === 0">
4040
<tr-welcome title="AI助手" description="您好,我是您的开发小助手" :icon="welcomeIcon" class="robot-welcome">
4141
</tr-welcome>
4242
<tr-prompts
@@ -46,19 +46,23 @@
4646
class="tiny-prompts"
4747
@item-click="handlePromptItemClick"
4848
></tr-prompts>
49-
</template>
50-
<tr-bubble-list v-else :items="activeMessages" :roles="roles"></tr-bubble-list>
49+
</div>
50+
<tr-bubble-provider :message-renderers="{ markdown: MarkdownRenderer }" v-else>
51+
<tr-bubble-list :items="activeMessages" :roles="roles" autoScroll></tr-bubble-list>
52+
</tr-bubble-provider>
5153
<template #footer>
5254
<tr-sender
53-
ref="senderRef"
55+
:maxlength="4000"
5456
mode="multiple"
5557
v-model="inputContent"
56-
placeholder="请输入问题或“/”唤起指令,支持粘贴文档"
57-
:clearable="true"
58-
:showWordLimit="true"
59-
:maxLength="1000"
58+
:autoSize="{ minRows: 1, maxRows: 5 }"
59+
placeholder="请输入您的问题..."
6060
@submit="sendContent(inputContent, false)"
61-
></tr-sender>
61+
>
62+
<template #footer-left>
63+
<mcp-server></mcp-server>
64+
</template>
65+
</tr-sender>
6266
</template>
6367
</tr-container>
6468
</div>
@@ -72,11 +76,15 @@ import { ref, onMounted, watchEffect, type CSSProperties, h, resolveComponent }
7276
import { Notify, Loading, TinyPopover } from '@opentiny/vue'
7377
import { useCanvas, useHistory, usePage, useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
7478
import { extend } from '@opentiny/vue-renderless/common/object'
75-
import { TrContainer, TrWelcome, TrPrompts, TrBubbleList, TrSender } from '@opentiny/tiny-robot'
79+
import { TrContainer, TrWelcome, TrPrompts, TrBubbleList, TrSender, TrBubbleProvider } from '@opentiny/tiny-robot'
7680
import type { BubbleRoleConfig, PromptProps } from '@opentiny/tiny-robot'
7781
import { IconNewSession } from '@opentiny/tiny-robot-svgs'
7882
import RobotSettingPopover from './RobotSettingPopover.vue'
7983
import { getBlockContent, initBlockList, AIModelOptions } from './js/robotSetting'
84+
import McpServer from './mcp/McpServer.vue'
85+
import useMcpServer from './mcp/useMcp'
86+
import MarkdownRenderer from './mcp/MarkdownRenderer.vue'
87+
import { sendMcpRequest } from './mcp/utils'
8088
8189
export default {
8290
components: {
@@ -87,7 +95,9 @@ export default {
8795
TrPrompts,
8896
TrBubbleList,
8997
TrSender,
90-
IconNewSession
98+
IconNewSession,
99+
McpServer,
100+
TrBubbleProvider
91101
},
92102
emits: ['close-chat'],
93103
setup() {
@@ -179,7 +189,22 @@ export default {
179189
content,
180190
name: 'AI'
181191
})
192+
182193
const sendRequest = () => {
194+
if (useMcpServer().isToolsEnabled) {
195+
try {
196+
sendMcpRequest(messages.value, {
197+
model: selectedModel.value.value,
198+
headers: {
199+
Authorization: `Bearer ${tokenValue.value || import.meta.env.VITE_API_TOKEN}`
200+
}
201+
})
202+
} catch (error) {
203+
messages.value[messages.value.length - 1].content = '连接失败'
204+
inProcesing.value = false
205+
}
206+
return
207+
}
183208
getMetaApi(META_SERVICE.Http)
184209
.post('/app-center/api/ai/chat', getSendSeesionProcess(), { timeout: 600000 })
185210
.then((res) => {
@@ -203,6 +228,7 @@ export default {
203228
connectedFailed.value = false
204229
})
205230
}
231+
206232
const scrollContent = async () => {
207233
await sleep(100)
208234
const scrollElement = document.getElementById('chatgpt-window')
@@ -305,6 +331,7 @@ export default {
305331
const endContent = () => {
306332
localStorage.removeItem('aiChat')
307333
sessionProcess = null
334+
inProcesing.value = false
308335
initChat()
309336
}
310337
@@ -345,6 +372,12 @@ export default {
345372
346373
// 欢迎界面提示
347374
const promptItems: PromptProps[] = [
375+
{
376+
label: 'MCP工具',
377+
description: '帮我查询当前的页面列表',
378+
icon: h('span', { style: { fontSize: '18px' } as CSSProperties }, '🔧'),
379+
badge: 'NEW'
380+
},
348381
{
349382
label: '页面搭建场景',
350383
description: '如何生成表单嵌进我的网站?',
@@ -378,8 +411,8 @@ export default {
378411
379412
// 对话角色配置
380413
const roles: Record<string, BubbleRoleConfig> = {
381-
assistant: { placement: 'start', avatar: aiAvatar, maxWidth: '80%' },
382-
user: { placement: 'end', avatar: userAvatar, maxWidth: '80%' }
414+
assistant: { placement: 'start', avatar: aiAvatar, maxWidth: '90%' },
415+
user: { placement: 'end', avatar: userAvatar, maxWidth: '90%' }
383416
}
384417
385418
return {
@@ -404,7 +437,8 @@ export default {
404437
promptItems,
405438
handlePromptItemClick,
406439
welcomeIcon,
407-
roles
440+
roles,
441+
MarkdownRenderer
408442
}
409443
}
410444
}
@@ -417,6 +451,7 @@ export default {
417451
align-items: center;
418452
width: 26px;
419453
height: 26px;
454+
420455
.chatgpt-icon {
421456
width: 18px;
422457
height: 18px;
@@ -431,11 +466,6 @@ export default {
431466
432467
.tiny-container {
433468
top: var(--base-top-panel-height) !important;
434-
background-image: linear-gradient(
435-
var(--te-chat-bg-top-color),
436-
var(--te-chat-bg-mid-color),
437-
var(--te-chat-bg-bottom-color)
438-
);
439469
container-type: inline-size;
440470
441471
:deep(button.icon-btn) {
@@ -446,6 +476,21 @@ export default {
446476
margin-left: 10px;
447477
}
448478
479+
.tr-bubble__content-messages {
480+
font-size: 14px;
481+
.tr-bubble__step-tool {
482+
word-break: break-all !important;
483+
}
484+
}
485+
486+
.tiny-sender__container .tiny-textarea__inner {
487+
font-size: 16px;
488+
}
489+
490+
.tr-bubble-list {
491+
flex: 1;
492+
}
493+
449494
.robot-welcome > div {
450495
display: flex;
451496
align-items: center;
@@ -457,12 +502,13 @@ export default {
457502
padding: 4px;
458503
}
459504
460-
.tiny-prompts {
505+
.tiny-prompts > div {
461506
padding: 16px 24px;
462507
463508
.prompt-item {
464509
width: 100%;
465510
box-sizing: border-box;
511+
466512
@container (width >=64rem) {
467513
width: calc(50% - 8px);
468514
}
@@ -471,8 +517,9 @@ export default {
471517
font-size: 14px;
472518
line-height: 24px;
473519
}
520+
474521
&:hover {
475-
background-color: #c3d3f6;
522+
background-color: #f8f8f8;
476523
}
477524
}
478525
}
@@ -502,5 +549,13 @@ export default {
502549
font-size: 20px;
503550
}
504551
}
552+
553+
.tiny-sender {
554+
margin: 20px;
555+
556+
.tiny-sender__footer-slot.tiny-sender__bottom-row {
557+
justify-content: space-between !important;
558+
}
559+
}
505560
}
506561
</style>

packages/plugins/robot/src/js/robotSetting.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import { reactive } from 'vue'
1515
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
1616

1717
export const AIModelOptions = [
18-
{ label: 'ChatGPT:gpt-3.5-turbo', value: 'gpt-3.5-turbo', manufacturer: 'openai' },
19-
{ label: '文心一言:ERNIE-4.0-8K', value: 'ERNIE-4.0-8K', manufacturer: 'baiduai' },
18+
{ label: 'SiliconFlow:DeepSeek-V3', value: 'deepseek-ai/DeepSeek-V3', manufacturer: 'siliconflow' },
2019
{ label: 'DeepSeek:DeepSeek-V3', value: 'deepseek-chat', manufacturer: 'deepseek' }
2120
]
2221

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<div v-html="renderContent"></div>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import DOMPurify from 'dompurify'
7+
import { default as MarkdownIt } from 'markdown-it'
8+
import { computed } from 'vue'
9+
10+
const md = new MarkdownIt({
11+
html: true,
12+
breaks: true,
13+
typographer: true
14+
})
15+
const props = defineProps<{
16+
type: 'markdown' | 'text'
17+
content: string
18+
}>()
19+
20+
const renderContent = computed(() => {
21+
const htmlContent = md.render(props.content)
22+
return DOMPurify.sanitize(htmlContent)
23+
})
24+
</script>
25+
26+
<style lang="less" scoped></style>

0 commit comments

Comments
 (0)