From f5bebb755de3206bdc5bacca0c6148b3c6800907 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 31 三月 2026 16:43:53 +0800
Subject: [PATCH] #前端
---
rsf-design/src/components/core/layouts/art-chat-window/index.vue | 15 ++-
rsf-design/src/components/core/layouts/art-chat-window/modules/markdown-message.vue | 198 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 207 insertions(+), 6 deletions(-)
diff --git a/rsf-design/src/components/core/layouts/art-chat-window/index.vue b/rsf-design/src/components/core/layouts/art-chat-window/index.vue
index 10efb89..52e8f78 100644
--- a/rsf-design/src/components/core/layouts/art-chat-window/index.vue
+++ b/rsf-design/src/components/core/layouts/art-chat-window/index.vue
@@ -313,12 +313,14 @@
: 'rounded-bl-xl bg-[var(--art-main-bg-color)] text-[var(--art-gray-900)] ring-1 ring-[var(--el-border-color-extra-light)]'
"
>
- <div class="whitespace-pre-wrap break-words">
- {{
- message.role === 'assistant'
- ? message.content || (streaming && index === messages.length - 1 ? $t('ai.drawer.thinking') : '')
- : message.content || ''
- }}
+ <MarkdownMessage
+ v-if="message.role === 'assistant'"
+ :content="
+ message.content || (streaming && index === messages.length - 1 ? $t('ai.drawer.thinking') : '')
+ "
+ />
+ <div v-else class="whitespace-pre-wrap break-words">
+ {{ message.content || '' }}
</div>
</div>
</div>
@@ -403,6 +405,7 @@
</template>
<script setup>
+ import MarkdownMessage from './modules/markdown-message.vue'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useWindowSize } from '@vueuse/core'
diff --git a/rsf-design/src/components/core/layouts/art-chat-window/modules/markdown-message.vue b/rsf-design/src/components/core/layouts/art-chat-window/modules/markdown-message.vue
new file mode 100644
index 0000000..303550e
--- /dev/null
+++ b/rsf-design/src/components/core/layouts/art-chat-window/modules/markdown-message.vue
@@ -0,0 +1,198 @@
+<template>
+ <div class="markdown-body assistant-markdown break-words" v-html="renderedContent"></div>
+</template>
+
+<script setup>
+ const props = defineProps({
+ content: {
+ type: String,
+ default: ''
+ }
+ })
+
+ const renderedContent = computed(() => renderMarkdown(props.content))
+
+ function renderMarkdown(content) {
+ const normalized = normalizeMarkdownSource(content)
+ if (!normalized) {
+ return ''
+ }
+
+ const lines = normalized.split('\n')
+ const html = []
+
+ for (let index = 0; index < lines.length; ) {
+ const line = lines[index].trim()
+
+ if (!line) {
+ index += 1
+ continue
+ }
+
+ if (/^```/.test(line)) {
+ const codeLines = []
+ index += 1
+ while (index < lines.length && !/^```/.test(lines[index].trim())) {
+ codeLines.push(lines[index])
+ index += 1
+ }
+ if (index < lines.length) {
+ index += 1
+ }
+ html.push(`<pre><code>${escapeHtml(codeLines.join('\n'))}</code></pre>`)
+ continue
+ }
+
+ if (/^\s*#{1,6}\s+/.test(line)) {
+ html.push(renderHeadingLine(line))
+ index += 1
+ continue
+ }
+
+ if (/^\s*[-*]\s+/.test(line)) {
+ const listLines = []
+ while (index < lines.length && /^\s*[-*]\s+/.test(lines[index].trim())) {
+ listLines.push(lines[index].trim())
+ index += 1
+ }
+ html.push(`<ul>${listLines
+ .map((item) => `<li>${renderMarkdownInline(item.replace(/^\s*[-*]\s+/, ''))}</li>`)
+ .join('')}</ul>`)
+ continue
+ }
+
+ if (/^\s*\d+\.\s+/.test(line)) {
+ const listLines = []
+ while (index < lines.length && /^\s*\d+\.\s+/.test(lines[index].trim())) {
+ listLines.push(lines[index].trim())
+ index += 1
+ }
+ html.push(`<ol>${listLines
+ .map((item) => `<li>${renderMarkdownInline(item.replace(/^\s*\d+\.\s+/, ''))}</li>`)
+ .join('')}</ol>`)
+ continue
+ }
+
+ if (/^\s*>\s?/.test(line)) {
+ const quoteLines = []
+ while (index < lines.length && /^\s*>\s?/.test(lines[index].trim())) {
+ quoteLines.push(lines[index].trim().replace(/^\s*>\s?/, ''))
+ index += 1
+ }
+ html.push(`<blockquote>${quoteLines.map((item) => `<p>${renderMarkdownInline(item)}</p>`).join('')}</blockquote>`)
+ continue
+ }
+
+ const paragraphLines = []
+ while (index < lines.length) {
+ const current = lines[index].trim()
+ if (
+ !current ||
+ /^```/.test(current) ||
+ /^\s*#{1,6}\s+/.test(current) ||
+ /^\s*[-*]\s+/.test(current) ||
+ /^\s*\d+\.\s+/.test(current) ||
+ /^\s*>\s?/.test(current)
+ ) {
+ break
+ }
+ paragraphLines.push(current)
+ index += 1
+ }
+
+ if (paragraphLines.length) {
+ html.push(`<p>${paragraphLines.map((item) => renderMarkdownInline(item)).join('<br>')}</p>`)
+ } else {
+ index += 1
+ }
+ }
+
+ return html.join('')
+ }
+
+ function normalizeMarkdownSource(content) {
+ return String(content || '')
+ .replace(/\r\n/g, '\n')
+ .replace(/<br\s*\/?>/gi, '\n')
+ .replace(/<\/p\s*>/gi, '\n\n')
+ .replace(/<p[^>]*>/gi, '')
+ .replace(/<\/div\s*>/gi, '\n')
+ .replace(/<div[^>]*>/gi, '')
+ .replace(/ /gi, ' ')
+ .replace(/\n{3,}/g, '\n\n')
+ .trim()
+ }
+
+ function renderHeadingLine(line) {
+ const level = Math.min((line.match(/^(\s*#+)/)?.[0].trim().length || 1), 6)
+ const title = line.replace(/^\s*#{1,6}\s+/, '')
+ return `<h${level}>${renderMarkdownInline(title)}</h${level}>`
+ }
+
+ function renderMarkdownInline(value) {
+ let result = escapeHtml(value)
+ result = result.replace(/`([^`]+)`/g, '<code>$1</code>')
+ result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
+ result = result.replace(/\*([^*\n]+)\*/g, '<em>$1</em>')
+ result = result.replace(
+ /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
+ '<a href="$2" target="_blank" rel="noreferrer noopener">$1</a>'
+ )
+ return result
+ }
+
+ function escapeHtml(value) {
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ }
+</script>
+
+<style lang="scss">
+ @use '@styles/core/md.scss';
+</style>
+
+<style scoped>
+ :deep(.assistant-markdown) {
+ color: var(--art-gray-800);
+ }
+
+ :deep(.assistant-markdown > :first-child) {
+ margin-top: 0;
+ }
+
+ :deep(.assistant-markdown > :last-child) {
+ margin-bottom: 0;
+ }
+
+ :deep(.assistant-markdown p) {
+ margin-bottom: 0.6rem;
+ }
+
+ :deep(.assistant-markdown ul),
+ :deep(.assistant-markdown ol) {
+ padding-left: 1.25rem;
+ }
+
+ :deep(.assistant-markdown pre) {
+ overflow-x: auto;
+ border-radius: 14px;
+ background: var(--el-fill-color-light);
+ padding: 0.875rem 1rem;
+ }
+
+ :deep(.assistant-markdown code) {
+ border-radius: 8px;
+ background: var(--el-fill-color-light);
+ padding: 0.1rem 0.35rem;
+ font-size: 0.875em;
+ }
+
+ :deep(.assistant-markdown pre code) {
+ background: transparent;
+ padding: 0;
+ }
+</style>
--
Gitblit v1.9.1