Skip to content

Commit 94b6cde

Browse files
committed
fix(ai): fix ai plugin mcp issue
1 parent 61bee59 commit 94b6cde

6 files changed

Lines changed: 249 additions & 135 deletions

File tree

packages/plugins/robot/src/Main.vue

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,27 @@
3636
<icon-new-session />
3737
</button>
3838
</template>
39-
<div v-if="activeMessages.length === 0">
40-
<tr-welcome title="AI助手" description="您好,我是您的开发小助手" :icon="welcomeIcon" class="robot-welcome">
41-
</tr-welcome>
42-
<tr-prompts
43-
:items="promptItems"
44-
:wrap="true"
45-
item-class="prompt-item"
46-
class="tiny-prompts"
47-
@item-click="handlePromptItemClick"
48-
></tr-prompts>
39+
<div class="robot-chat-container-content" ref="chatContainerRef">
40+
<div v-if="activeMessages.length === 0">
41+
<tr-welcome
42+
title="AI助手"
43+
description="您好,我是您的开发小助手"
44+
:icon="welcomeIcon"
45+
class="robot-welcome"
46+
>
47+
</tr-welcome>
48+
<tr-prompts
49+
:items="promptItems"
50+
:wrap="true"
51+
item-class="prompt-item"
52+
class="tiny-prompts"
53+
@item-click="handlePromptItemClick"
54+
></tr-prompts>
55+
</div>
56+
<tr-bubble-provider :content-renderers="{ markdown: MarkdownRenderer }" v-else>
57+
<tr-bubble-list :items="activeMessages" :roles="roles" autoScroll></tr-bubble-list>
58+
</tr-bubble-provider>
4959
</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>
5360
<template #footer>
5461
<tr-sender
5562
:maxlength="4000"
@@ -61,7 +68,7 @@
6168
@submit="sendContent(inputContent, false)"
6269
>
6370
<template #footer-left>
64-
<mcp-server></mcp-server>
71+
<mcp-server :position="mcpDrawerPosition"></mcp-server>
6572
</template>
6673
</tr-sender>
6774
</template>
@@ -73,7 +80,7 @@
7380

7481
<script lang="ts">
7582
/* metaService: engine.plugins.robot.Main */
76-
import { ref, onMounted, watchEffect, type CSSProperties, h, resolveComponent } from 'vue'
83+
import { ref, onMounted, watchEffect, type CSSProperties, h, resolveComponent, computed, watch } from 'vue'
7784
import { Notify, Loading, TinyPopover } from '@opentiny/vue'
7885
import { useCanvas, useHistory, usePage, useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
7986
import { extend } from '@opentiny/vue-renderless/common/object'
@@ -118,6 +125,8 @@ export default {
118125
const tokenValue = ref('')
119126
const showPopover = ref(false)
120127
128+
const chatContainerRef = ref(null)
129+
121130
const { pageSettingState, getDefaultPage } = usePage()
122131
const ROOT_ID = pageSettingState.ROOT_ID
123132
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
@@ -188,8 +197,7 @@ export default {
188197
189198
const getAiRespMessage = (role = 'assistant', content) => ({
190199
role,
191-
content,
192-
name: 'AI'
200+
content
193201
})
194202
195203
const requestLoading = ref(false)
@@ -198,6 +206,7 @@ export default {
198206
if (useMcpServer().isToolsEnabled) {
199207
try {
200208
requestLoading.value = true
209+
await scrollContent() // eslint-disable-line
201210
await sendMcpRequest(messages.value, {
202211
model: selectedModel.value.value,
203212
headers: {
@@ -209,6 +218,7 @@ export default {
209218
} finally {
210219
inProcesing.value = false
211220
requestLoading.value = false
221+
await scrollContent() // eslint-disable-line
212222
}
213223
return
214224
}
@@ -237,10 +247,8 @@ export default {
237247
}
238248
239249
const scrollContent = async () => {
240-
await sleep(100)
241-
const scrollElement = document.getElementById('chatgpt-window')
242-
if (scrollElement) {
243-
scrollElement.scrollTop = scrollElement.scrollHeight
250+
if (chatContainerRef.value) {
251+
chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight
244252
}
245253
}
246254
@@ -257,8 +265,7 @@ export default {
257265
258266
const getMessage = (content) => ({
259267
role: 'user',
260-
content,
261-
name: 'John'
268+
content
262269
})
263270
264271
const sendContent = async (content, isModel) => {
@@ -412,11 +419,33 @@ export default {
412419
413420
// 对话角色配置
414421
const roles: Record<string, BubbleRoleConfig> = {
415-
assistant: { placement: 'start', avatar: aiAvatar, maxWidth: '90%', contentRenderer: MarkdownRenderer },
422+
assistant: {
423+
placement: 'start',
424+
avatar: aiAvatar,
425+
maxWidth: '90%',
426+
contentRenderer: MarkdownRenderer,
427+
customContentField: 'renderContent'
428+
},
416429
user: { placement: 'end', avatar: userAvatar, maxWidth: '90%', contentRenderer: MarkdownRenderer }
417430
}
418431
432+
watch([activeMessages.value.length, activeMessages.value.at(-1)?.renderContent?.length], async () => {
433+
await scrollContent()
434+
})
435+
436+
const mcpDrawerPosition = computed(() => {
437+
return {
438+
type: 'fixed',
439+
position: {
440+
top: 'var(--base-top-panel-height)',
441+
bottom: 0,
442+
...(fullscreen.value ? { left: 0 } : { right: 'var(--tr-container-width)' })
443+
}
444+
}
445+
})
446+
419447
return {
448+
chatContainerRef,
420449
robotVisible,
421450
avatarUrl,
422451
chatWindowOpened,
@@ -440,7 +469,8 @@ export default {
440469
welcomeIcon,
441470
roles,
442471
MarkdownRenderer,
443-
requestLoading
472+
requestLoading,
473+
mcpDrawerPosition
444474
}
445475
}
446476
}
@@ -486,10 +516,38 @@ export default {
486516
}
487517
488518
.tr-bubble-list {
519+
font-size: 14px;
489520
flex: 1;
490521
.tr-bubble {
491522
word-break: break-word;
492523
}
524+
ul,
525+
ol {
526+
padding-left: 10px;
527+
}
528+
ul > li {
529+
list-style: disc;
530+
}
531+
ol > li {
532+
list-style: decimal;
533+
}
534+
table {
535+
border-collapse: collapse; // 合并边框
536+
border: 1px solid #ccc;
537+
width: 100%;
538+
margin: 1rem 0;
539+
th,
540+
td {
541+
border: 1px solid #ccc; /* 单元格边框 */
542+
padding: 8px;
543+
}
544+
tr:nth-child(even) {
545+
background-color: #f2f2f2;
546+
}
547+
tr:hover {
548+
background-color: #e6f7ff;
549+
}
550+
}
493551
}
494552
495553
.robot-welcome > div {

packages/plugins/robot/src/mcp/MarkdownRenderer.vue

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,131 @@
11
<template>
2-
<div v-html="renderContent" class="robot-markdown-renderer"></div>
2+
<div v-html="renderContent" class="markdown-renderer" :class="themeClass"></div>
33
</template>
44

55
<script setup lang="ts">
6+
import { computed, watch, onMounted, nextTick } from 'vue'
67
import DOMPurify from 'dompurify'
7-
import MarkdownIt, { type Options as MarkdownItOptions } from 'markdown-it'
8-
import hljs from 'highlight.js'
9-
import { computed } from 'vue'
10-
import 'highlight.js/styles/github.min.css'
8+
import MarkdownIt, { type Options } from 'markdown-it'
9+
import hljs from 'highlight.js/lib/core'
10+
import 'highlight.js/styles/github.css'
11+
import 'highlight.js/styles/github-dark.css'
1112
12-
const defaultMarkdownItOptions = {
13+
// 按需加载语言
14+
import bash from 'highlight.js/lib/languages/bash'
15+
import javascript from 'highlight.js/lib/languages/javascript'
16+
import typescript from 'highlight.js/lib/languages/typescript'
17+
import json from 'highlight.js/lib/languages/json'
18+
import yaml from 'highlight.js/lib/languages/yaml'
19+
import xml from 'highlight.js/lib/languages/xml'
20+
import shell from 'highlight.js/lib/languages/shell'
21+
22+
// 注册语言
23+
hljs.registerLanguage('bash', bash)
24+
hljs.registerLanguage('javascript', javascript)
25+
hljs.registerLanguage('typescript', typescript)
26+
hljs.registerLanguage('json', json)
27+
hljs.registerLanguage('yaml', yaml)
28+
hljs.registerLanguage('xml', xml)
29+
hljs.registerLanguage('shell', shell)
30+
31+
const props = defineProps({
32+
content: {
33+
type: String,
34+
required: true
35+
},
36+
theme: {
37+
type: String as () => 'light' | 'dark',
38+
default: 'dark'
39+
},
40+
options: {
41+
type: Object as () => Options,
42+
default: () => ({})
43+
}
44+
})
45+
46+
const themeClass = computed(() => `hljs-theme-${props.theme}`)
47+
48+
const markdownIt = new MarkdownIt({
49+
html: true,
1350
breaks: true,
1451
typographer: true,
15-
highlight: function (str: string, lang: string) {
52+
highlight: (str: string, lang: string) => {
1653
if (lang && hljs.getLanguage(lang)) {
1754
try {
18-
return hljs.highlightAuto(str).value
19-
} catch (__) {
55+
return `<pre class="hljs"><code>${
56+
hljs.highlight(str, {
57+
language: lang,
58+
ignoreIllegals: true
59+
}).value
60+
}</code></pre>`
61+
} catch (e) {
2062
/* ignore */
2163
}
2264
}
65+
return `<pre class="hljs"><code>${DOMPurify.sanitize(str)}</code></pre>`
66+
},
67+
...props.options
68+
})
2369
24-
return str
25-
}
26-
}
70+
const renderContent = computed(() => {
71+
return DOMPurify.sanitize(markdownIt.render(props.content))
72+
})
2773
28-
const props = defineProps<{
29-
type?: 'markdown' | 'text'
30-
content: string
31-
options?: MarkdownItOptions
32-
}>()
33-
34-
const markdownIt = computed(
35-
() =>
36-
new MarkdownIt({
37-
...defaultMarkdownItOptions,
38-
...props.options
74+
// 动态切换主题时重新高亮
75+
watch(
76+
() => props.theme,
77+
() => {
78+
nextTick(() => {
79+
document.querySelectorAll('.markdown-renderer pre code').forEach((el) => {
80+
hljs.highlightElement(el as HTMLElement)
81+
})
3982
})
83+
}
4084
)
4185
42-
const renderContent = computed(() => {
43-
const htmlContent = markdownIt.value.render(props.content)
44-
return DOMPurify.sanitize(htmlContent)
86+
onMounted(() => {
87+
nextTick(() => {
88+
document.querySelectorAll('.markdown-renderer pre code').forEach((el) => {
89+
hljs.highlightElement(el as HTMLElement)
90+
})
91+
})
4592
})
4693
</script>
4794

48-
<style lang="less" scoped>
49-
.robot-markdown-renderer {
95+
<style lang="less">
96+
.markdown-renderer {
5097
word-break: break-word;
51-
& > *:first-child {
98+
99+
pre {
100+
border-radius: 6px;
101+
padding: 1em;
102+
overflow: auto;
103+
line-height: 1.45;
104+
}
105+
106+
/* 亮色主题 */
107+
&.hljs-theme-light {
108+
@import 'highlight.js/styles/github.css';
109+
110+
pre {
111+
background-color: #f6f8fa;
112+
}
113+
}
114+
115+
/* 暗色主题 */
116+
&.hljs-theme-dark {
117+
@import 'highlight.js/styles/github-dark.css';
118+
119+
pre {
120+
background-color: #0d1117;
121+
}
122+
}
123+
124+
> *:first-child {
52125
margin-top: 0 !important;
53126
}
54-
& > *:last-child {
127+
128+
> *:last-child {
55129
margin-bottom: 0 !important;
56130
}
57131
}

0 commit comments

Comments
 (0)