| <script lang="ts" setup> | |
| import { computed, ref } from 'vue' | |
| import MarkdownIt from 'markdown-it' | |
| import mdKatex from '@traptitech/markdown-it-katex' | |
| import mila from 'markdown-it-link-attributes' | |
| import hljs from 'highlight.js' | |
| import { useBasicLayout } from '@/hooks/useBasicLayout' | |
| import { t } from '@/locales' | |
| interface Props { | |
| inversion?: boolean | |
| error?: boolean | |
| text?: string | |
| loading?: boolean | |
| asRawText?: boolean | |
| } | |
| const props = defineProps<Props>() | |
| const { isMobile } = useBasicLayout() | |
| const textRef = ref<HTMLElement>() | |
| const mdi = new MarkdownIt({ | |
| linkify: true, | |
| highlight(code, language) { | |
| const validLang = !!(language && hljs.getLanguage(language)) | |
| if (validLang) { | |
| const lang = language ?? '' | |
| return highlightBlock(hljs.highlight(code, { language: lang }).value, lang) | |
| } | |
| return highlightBlock(hljs.highlightAuto(code).value, '') | |
| }, | |
| }) | |
| mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } }) | |
| mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' }) | |
| const wrapClass = computed(() => { | |
| return [ | |
| 'text-wrap', | |
| 'min-w-[20px]', | |
| 'rounded-md', | |
| isMobile.value ? 'p-2' : 'px-3 py-2', | |
| props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]', | |
| props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]', | |
| props.inversion ? 'message-request' : 'message-reply', | |
| { 'text-red-500': props.error }, | |
| ] | |
| }) | |
| const text = computed(() => { | |
| const value = props.text ?? '' | |
| if (!props.asRawText) | |
| return mdi.render(value) | |
| return value | |
| }) | |
| function highlightBlock(str: string, lang?: string) { | |
| return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>` | |
| } | |
| defineExpose({ textRef }) | |
| </script> | |
| <template> | |
| <div class="text-black" :class="wrapClass"> | |
| <template v-if="loading"> | |
| <span class="dark:text-white w-[4px] h-[20px] block animate-blink" /> | |
| </template> | |
| <template v-else> | |
| <div ref="textRef" class="leading-relaxed break-words"> | |
| <div v-if="!inversion"> | |
| <div v-if="!asRawText" class="markdown-body" v-html="text" /> | |
| <div v-else class="whitespace-pre-wrap" v-text="text" /> | |
| </div> | |
| <div v-else class="whitespace-pre-wrap" v-text="text" /> | |
| </div> | |
| </template> | |
| </div> | |
| </template> | |
| <style lang="less"> | |
| @import url(./style.less); | |
| </style> | |