Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
水玉婷
ai-wechat
Commits
6d548eb4
Commit
6d548eb4
authored
Nov 27, 2025
by
水玉婷
Browse files
feat:添加markdown 模版
parent
e32996f4
Changes
3
Hide whitespace changes
Inline
Side-by-side
src/utils/axios.js
View file @
6d548eb4
...
...
@@ -3,7 +3,7 @@ import axios from 'axios'
// 创建axios实例
const
instance
=
axios
.
create
({
baseURL
:
'
/
'
,
timeout
:
1
0000
,
timeout
:
6
0000
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
}
...
...
src/views/components/AiChat.vue
View file @
6d548eb4
...
...
@@ -6,7 +6,7 @@
<img
:src=
"props.logoUrl || defaultAvatar"
alt=
"avatar"
class=
"avatar-image"
/>
</div>
<div
class=
"header-info"
>
<h2>
{{
props
.
dialogSessionId
?
props
?.
detailData
?.
title
||
'
继续对话
'
:
'
新建对话
'
}}
</h2>
<h2>
{{
props
.
dialogSessionId
?
props
?.
detailData
?.
title
||
'
继续对话
'
:
'
新建对话
'
}}
</h2>
</div>
</div>
...
...
@@ -88,7 +88,7 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
import
{
ref
,
onMounted
,
onBeforeUnmount
,
nextTick
,
watch
}
from
'
vue
'
;
import
dayjs
from
'
dayjs
'
;
import
{
post
,
get
}
from
'
@/utils/axios.js
'
;
// 导入axios的post方法
import
tableTemplate
from
'
./tableTemplate
'
;
import
{
tableTemplate
}
from
'
./tableTemplate
'
;
import
{
SendOutlined
,
UserOutlined
}
from
'
@ant-design/icons-vue
'
;
import
defaultAvatar
from
'
@/assets/logo.png
'
;
import
ChartComponent
from
'
./ChartComponent.vue
'
;
// 导入独立的图表组件
...
...
@@ -248,6 +248,88 @@ const contentTemplates = {
</div>
<audio id="
${
audioId
}
" src="
${
src
}
" preload="metadata" style="display: none;"></audio>
</div>`
;
},
// Markdown模板
markdown
:
(
content
:
any
)
=>
{
// 类型检查和默认值处理
if
(
typeof
content
!==
'
string
'
)
{
// 如果是对象,尝试转换为字符串
if
(
content
&&
typeof
content
===
'
object
'
)
{
content
=
JSON
.
stringify
(
content
);
}
else
{
// 其他类型转换为字符串
content
=
String
(
content
||
''
);
}
}
// 简单的Markdown解析器
const
parseMarkdown
=
(
text
:
string
)
=>
{
// 确保text是字符串
if
(
typeof
text
!==
'
string
'
)
{
text
=
String
(
text
||
''
);
}
// 处理标题
text
=
text
.
replace
(
/^###
(
.*$
)
/gim
,
'
<h3>$1</h3>
'
);
text
=
text
.
replace
(
/^##
(
.*$
)
/gim
,
'
<h2>$1</h2>
'
);
text
=
text
.
replace
(
/^#
(
.*$
)
/gim
,
'
<h1>$1</h1>
'
);
// 处理粗体
text
=
text
.
replace
(
/
\*\*(
.*
?)\*\*
/gim
,
'
<strong>$1</strong>
'
);
text
=
text
.
replace
(
/
\*(
.*
?)\*
/gim
,
'
<em>$1</em>
'
);
// 处理代码块
text
=
text
.
replace
(
/```
([\s\S]
*
?)
```/gim
,
'
<pre><code>$1</code></pre>
'
);
text
=
text
.
replace
(
/`
(
.*
?)
`/gim
,
'
<code>$1</code>
'
);
// 处理链接
text
=
text
.
replace
(
/
\[([^\[]
+
)\]\(([^\)]
+
)\)
/gim
,
'
<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>
'
);
// 处理图片
text
=
text
.
replace
(
/!
\[([^\[]
+
)\]\(([^\)]
+
)\)
/gim
,
'
<img src="$2" alt="$1" style="max-width: 100%; height: auto;" />
'
);
// 处理列表
text
=
text
.
replace
(
/^
\s
*-
\s(
.*$
)
/gim
,
'
<li>$1</li>
'
);
text
=
text
.
replace
(
/
(
<li>.*<
\/
li>
)
/gim
,
'
<ul>$1</ul>
'
);
// 处理换行
text
=
text
.
replace
(
/
\n
/gim
,
'
<br>
'
);
// 处理段落
text
=
text
.
replace
(
/<br><br>/gim
,
'
</p><p>
'
);
text
=
'
<p>
'
+
text
+
'
</p>
'
;
text
=
text
.
replace
(
/<p><
(
h
[
1-6
]
|ul|pre|img
)
/gim
,
'
</p><$1
'
);
text
=
text
.
replace
(
/
(
<
\/(
h
[
1-6
]
|ul|pre|img
)
>
)
<p>/gim
,
'
$1</p><p>
'
);
return
text
;
};
const
htmlContent
=
parseMarkdown
(
content
);
// 清理HTML内容:移除br标签和空p段落
const
cleanHtml
=
htmlContent
.
trim
()
// 移除所有
<
br
>
标签
.
replace
(
/<br
\s
*
\/?
>/gi
,
''
)
// 移除空的
<
p
>
段落
(
只包含空格或换行符
)
.
replace
(
/<p
[^
>
]
*>
\s
*<
\/
p>/gi
,
''
)
// 移除只包含空格的
<
p
>
段落
.
replace
(
/<p
[^
>
]
*>
(\s
|
)
*<
\/
p>/gi
,
''
)
// 移除连续的空白段落
.
replace
(
/
(
<
\/
p>
\s
*<p
[^
>
]
*>
)
+/gi
,
''
)
// 移除开头和结尾的空白段落
.
replace
(
/^
\s
*<p
[^
>
]
*>
\s
*<
\/
p>/gi
,
''
)
.
replace
(
/<p
[^
>
]
*>
\s
*<
\/
p>
\s
*$/gi
,
''
)
.
trim
();
// 检查内容是否为空或只有空白
if
(
!
cleanHtml
||
cleanHtml
===
'
<p></p>
'
||
cleanHtml
===
'
<p> </p>
'
)
{
return
''
;
// 如果内容为空,返回空字符串不展示
}
return
`<div class="message-markdown">
<div class="markdown-content">
${
cleanHtml
}
</div>
</div>`
;
}
};
...
...
@@ -475,6 +557,44 @@ const processSSEMessage = (
// 根据是否为历史数据设置默认展开状态
const
defaultThinkBoxExpanded
=
!
isHistoryData
;
// 辅助函数:检查最后一个contentBlock是否是type为4的markdown块
const
isLastBlockMarkdown
=
()
=>
{
if
(
!
updatedResponse
||
updatedResponse
.
contentBlocks
.
length
===
0
)
{
return
false
;
}
const
lastBlock
=
updatedResponse
.
contentBlocks
[
updatedResponse
.
contentBlocks
.
length
-
1
];
return
lastBlock
.
content
.
includes
(
'
message-markdown
'
);
};
// 辅助函数:获取最后一个markdown块的索引
const
getLastMarkdownBlockIndex
=
()
=>
{
if
(
!
updatedResponse
||
updatedResponse
.
contentBlocks
.
length
===
0
)
{
return
-
1
;
}
for
(
let
i
=
updatedResponse
.
contentBlocks
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
updatedResponse
.
contentBlocks
[
i
].
content
.
includes
(
'
message-markdown
'
))
{
return
i
;
}
}
return
-
1
;
};
// 辅助函数:合并markdown内容
const
mergeMarkdownContent
=
(
existingContent
:
string
,
newContent
:
string
)
=>
{
// 从现有的markdown内容中提取内部内容
const
existingInnerContent
=
existingContent
.
replace
(
/<div class="message-markdown">
\s
*<div class="markdown-content">
([\s\S]
*
?)
<
\/
div>
\s
*<
\/
div>/
,
'
$1
'
);
// 从新的markdown内容中提取内部内容
const
newInnerContent
=
newContent
.
replace
(
/<div class="message-markdown">
\s
*<div class="markdown-content">
([\s\S]
*
?)
<
\/
div>
\s
*<
\/
div>/
,
'
$1
'
);
// 合并内容并重新包裹
const
mergedInnerContent
=
existingInnerContent
+
newInnerContent
;
return
`<div class="message-markdown">
<div class="markdown-content">
${
mergedInnerContent
}
</div>
</div>`
;
};
switch
(
contentStatus
)
{
case
-
1
:
// 错误信息
if
(
updatedResponse
)
{
...
...
@@ -531,8 +651,33 @@ const processSSEMessage = (
});
}
}
break
;
case
4
:
// MD格式
if
(
updatedResponse
)
{
const
markdownContent
=
contentTemplates
.
markdown
(
messageContent
||
''
);
// 检查最后一个块是否是markdown块
if
(
isLastBlockMarkdown
())
{
// 合并到现有的markdown块
const
lastMarkdownIndex
=
getLastMarkdownBlockIndex
();
if
(
lastMarkdownIndex
!==
-
1
)
{
updatedResponse
.
contentBlocks
[
lastMarkdownIndex
].
content
=
mergeMarkdownContent
(
updatedResponse
.
contentBlocks
[
lastMarkdownIndex
].
content
,
markdownContent
);
}
}
else
{
// 创建新的markdown块
updatedResponse
.
contentBlocks
.
push
({
content
:
markdownContent
,
hasThinkBox
:
false
,
thinkContent
:
''
,
thinkBoxExpanded
:
false
,
});
}
}
break
;
default
:
// 默认处理
updatedResponse
.
contentBlocks
.
push
({
content
:
contentTemplates
.
text
(
messageContent
||
''
),
...
...
@@ -543,7 +688,7 @@ const processSSEMessage = (
break
;
}
}
break
;
break
;
case
10
:
// 思考开始
updatedIsThinking
=
true
;
if
(
updatedBlockIndex
===
-
1
&&
updatedResponse
)
{
...
...
src/views/components/style.less
View file @
6d548eb4
...
...
@@ -319,7 +319,7 @@ p, h1, h2, h3, h4, h5, h6, ul, ol, li {
.think-line {
font-size: 13px;
color: @gray-
5
;
color: @gray-
7
;
font-style: italic;
}
}
...
...
@@ -530,7 +530,7 @@ text-overflow: ellipsis;
}
}
// 趋势箭头样式
// 趋势箭头样式
.trend-up {
color: @success-color;
font-weight: bold;
...
...
@@ -708,7 +708,7 @@ width: 100%;
}
100% {
transform: translateX(200%);
}
}
}
// =============================================
...
...
@@ -732,7 +732,7 @@ width: 100%;
}
.table-summary {
font-size: 12px;
font-size: 12px;
padding: 8px 10px;
}
}
...
...
@@ -932,6 +932,193 @@ width: 100%;
}
}
// =============================================
// Markdown消息样式
// =============================================
:deep(.message-markdown) {
width: 100%;
max-width: 100%;
margin: 8px 0;
border-radius: 8px;
background-color: @white;
border: 1px solid @blue-light-3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
.markdown-content {
padding: 12px 16px;
font-size: 14px;
line-height: 1.6;
color: @gray-7;
background: none;
// 标题样式
h1, h2, h3, h4, h5, h6 {
margin-bottom: 8px;
font-weight: 600;
color: @gray-7;
line-height: 1.3;
background: none;
}
h1 {
font-size: 20px;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
// 段落样式
p {
text-align: justify;
background: none;
margin-bottom: 8px;
}
// 链接样式
a {
color: @primary-color;
text-decoration: none;
border-bottom: 1px solid transparent;
transition: all 0.2s ease;
background: none;
&:hover {
color: @primary-hover;
border-bottom-color: @primary-hover;
}
}
// 代码样式
code {
background-color: @gray-2;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
color: @error-color;
}
pre {
background-color: @gray-1;
border: 1px solid @gray-3;
border-radius: 6px;
padding: 12px;
overflow-x: auto;
margin-bottom: 8px;
code {
background: none;
padding: 0;
color: @gray-7;
font-size: 13px;
line-height: 1.4;
}
}
// 列表样式
ul, ol {
padding-left: 24px;
margin-bottom: 8px;
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
// 粗体和斜体
strong {
font-weight: 600;
color: @gray-7;
}
em {
font-style: italic;
color: @gray-6;
}
// 图片样式
img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin-bottom: 8px;
}
// 引用块样式
blockquote {
border-left: 4px solid @primary-color;
margin-bottom: 8px;
padding: 0 16px;
font-style: italic;
color: @gray-6;
background: none;
}
// 表格样式(如果Markdown中包含表格)
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 8px;
th, td {
padding: 8px 12px;
border: 1px solid @gray-3;
text-align: left;
}
th {
background-color: @blue-light-2;
font-weight: 600;
}
tr:nth-child(even) {
background-color: @gray-1;
}
}
}
}
// 响应式调整
@media (max-width: 768px) {
:deep(.message-markdown) {
.markdown-content {
font-size: 13px;
padding: 10px 12px;
h1 {
font-size: 18px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 15px;
}
pre {
padding: 8px;
code {
font-size: 12px;
}
}
}
}
}
// 语音识别组件样式调整
.chat-input {
display: flex;
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment