Commit 0f8cc378 by zhugs

feat:chat markdown支持

parent 2174c94e
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"js-beautify": "1.15.4", "js-beautify": "1.15.4",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"jsencrypt": "3.3.2", "jsencrypt": "3.3.2",
"markdown-it": "^15.0.3",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.4", "pinia": "3.0.4",
"sortablejs": "^1.15.7", "sortablejs": "^1.15.7",
...@@ -39,6 +40,7 @@ ...@@ -39,6 +40,7 @@
"vuedraggable": "4.1.0" "vuedraggable": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/markdown-it": "^14.2.0",
"@vitejs/plugin-vue": "5.2.4", "@vitejs/plugin-vue": "5.2.4",
"@vue/tsconfig": "0.5.1", "@vue/tsconfig": "0.5.1",
"sass-embedded": "1.97.2", "sass-embedded": "1.97.2",
......
...@@ -50,6 +50,9 @@ importers: ...@@ -50,6 +50,9 @@ importers:
jsencrypt: jsencrypt:
specifier: 3.3.2 specifier: 3.3.2
version: 3.3.2 version: 3.3.2
markdown-it:
specifier: ^15.0.3
version: 15.0.3
nprogress: nprogress:
specifier: 0.2.0 specifier: 0.2.0
version: 0.2.0 version: 0.2.0
...@@ -75,6 +78,9 @@ importers: ...@@ -75,6 +78,9 @@ importers:
specifier: 4.1.0 specifier: 4.1.0
version: 4.1.0(vue@3.5.26(typescript@5.6.3)) version: 4.1.0(vue@3.5.26(typescript@5.6.3))
devDependencies: devDependencies:
'@types/markdown-it':
specifier: ^14.2.0
version: 14.2.0
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: 5.2.4 specifier: 5.2.4
version: 5.2.4(vite@6.4.1(@types/node@26.4.1)(sass-embedded@1.97.2)(sass@1.97.2))(vue@3.5.26(typescript@5.6.3)) version: 5.2.4(vite@6.4.1(@types/node@26.4.1)(sass-embedded@1.97.2)(sass@1.97.2))(vue@3.5.26(typescript@5.6.3))
...@@ -573,12 +579,21 @@ packages: ...@@ -573,12 +579,21 @@ packages:
'@types/estree@1.0.9': '@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.25': '@types/lodash@4.17.25':
resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==}
'@types/markdown-it@14.2.0':
resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/node@26.4.1': '@types/node@26.4.1':
resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==} resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==}
...@@ -745,6 +760,9 @@ packages: ...@@ -745,6 +760,9 @@ packages:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'} engines: {node: '>=12'}
argparse@3.0.4:
resolution: {integrity: sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==}
arr-diff@4.0.0: arr-diff@4.0.0:
resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
...@@ -1082,6 +1100,10 @@ packages: ...@@ -1082,6 +1100,10 @@ packages:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
entities@8.1.0:
resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==}
engines: {node: '>=20.19.0'}
es-abstract-get@1.0.0: es-abstract-get@1.0.0:
resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -1569,6 +1591,9 @@ packages: ...@@ -1569,6 +1591,9 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
linkify-it@6.1.0:
resolution: {integrity: sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==}
loader-utils@1.4.2: loader-utils@1.4.2:
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
...@@ -1619,6 +1644,10 @@ packages: ...@@ -1619,6 +1644,10 @@ packages:
resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
markdown-it@15.0.3:
resolution: {integrity: sha512-+Az7Ojz3iRGu8KYMvJ6xVhvrxP3eE+vEtJX75Ow88XBpcQNV/0lGX5ZynIIBftREo4DC2jsveAMDaeltmm8QvQ==}
hasBin: true
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -1626,6 +1655,9 @@ packages: ...@@ -1626,6 +1655,9 @@ packages:
mdn-data@2.0.14: mdn-data@2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
mdurl@2.1.0:
resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==}
memoize-one@6.0.0: memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
...@@ -1841,6 +1873,10 @@ packages: ...@@ -1841,6 +1873,10 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
quansync@0.2.11: quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
...@@ -2316,6 +2352,9 @@ packages: ...@@ -2316,6 +2352,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@3.0.0:
resolution: {integrity: sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==}
ufo@1.6.4: ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
...@@ -2796,12 +2835,21 @@ snapshots: ...@@ -2796,12 +2835,21 @@ snapshots:
'@types/estree@1.0.9': {} '@types/estree@1.0.9': {}
'@types/linkify-it@5.0.0': {}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
dependencies: dependencies:
'@types/lodash': 4.17.25 '@types/lodash': 4.17.25
'@types/lodash@4.17.25': {} '@types/lodash@4.17.25': {}
'@types/markdown-it@14.2.0':
dependencies:
'@types/linkify-it': 5.0.0
'@types/mdurl': 2.0.0
'@types/mdurl@2.0.0': {}
'@types/node@26.4.1': '@types/node@26.4.1':
dependencies: dependencies:
undici-types: 8.3.0 undici-types: 8.3.0
...@@ -3017,6 +3065,8 @@ snapshots: ...@@ -3017,6 +3065,8 @@ snapshots:
ansi-styles@6.2.3: {} ansi-styles@6.2.3: {}
argparse@3.0.4: {}
arr-diff@4.0.0: {} arr-diff@4.0.0: {}
arr-flatten@1.1.0: {} arr-flatten@1.1.0: {}
...@@ -3399,6 +3449,8 @@ snapshots: ...@@ -3399,6 +3449,8 @@ snapshots:
entities@7.0.1: {} entities@7.0.1: {}
entities@8.1.0: {}
es-abstract-get@1.0.0: es-abstract-get@1.0.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
...@@ -3985,6 +4037,10 @@ snapshots: ...@@ -3985,6 +4037,10 @@ snapshots:
kind-of@6.0.3: {} kind-of@6.0.3: {}
linkify-it@6.1.0:
dependencies:
uc.micro: 3.0.0
loader-utils@1.4.2: loader-utils@1.4.2:
dependencies: dependencies:
big.js: 5.2.2 big.js: 5.2.2
...@@ -4032,10 +4088,21 @@ snapshots: ...@@ -4032,10 +4088,21 @@ snapshots:
dependencies: dependencies:
object-visit: 1.0.1 object-visit: 1.0.1
markdown-it@15.0.3:
dependencies:
argparse: 3.0.4
entities: 8.1.0
linkify-it: 6.1.0
mdurl: 2.1.0
punycode.js: 2.3.1
uc.micro: 3.0.0
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mdn-data@2.0.14: {} mdn-data@2.0.14: {}
mdurl@2.1.0: {}
memoize-one@6.0.0: {} memoize-one@6.0.0: {}
merge-options@1.0.1: merge-options@1.0.1:
...@@ -4264,6 +4331,8 @@ snapshots: ...@@ -4264,6 +4331,8 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
punycode.js@2.3.1: {}
quansync@0.2.11: {} quansync@0.2.11: {}
query-string@4.3.4: query-string@4.3.4:
...@@ -4831,6 +4900,8 @@ snapshots: ...@@ -4831,6 +4900,8 @@ snapshots:
typescript@5.6.3: {} typescript@5.6.3: {}
uc.micro@3.0.0: {}
ufo@1.6.4: {} ufo@1.6.4: {}
unbox-primitive@1.1.0: unbox-primitive@1.1.0:
......
<script setup lang="ts" name="AiChat"> <script setup lang="ts" name="AiChat">
import { listKnowledgeOptions, askKnowledgeStream } from '@/api/ai/knowledge' import { listKnowledgeOptions, askKnowledgeStream } from '@/api/ai/knowledge'
import MarkdownIt from 'markdown-it'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const markdownRenderer = new MarkdownIt({
html: false,
breaks: true,
linkify: true
})
const kbOptions = ref([]) const kbOptions = ref([])
const kbId = ref(null) const kbId = ref(null)
...@@ -139,7 +145,11 @@ loadOptions() ...@@ -139,7 +145,11 @@ loadOptions()
<div v-if="messages.length === 0" class="chat-empty">请选择知识库并提问,例如:降温通风的判定条件是什么?</div> <div v-if="messages.length === 0" class="chat-empty">请选择知识库并提问,例如:降温通风的判定条件是什么?</div>
<div v-for="(item, index) in messages" :key="index" :class="['chat-item', item.role]"> <div v-for="(item, index) in messages" :key="index" :class="['chat-item', item.role]">
<div class="chat-role">{{ item.role === 'user' ? '我' : '知识库' }}</div> <div class="chat-role">{{ item.role === 'user' ? '我' : '知识库' }}</div>
<div class="chat-content">{{ item.content }}<span v-if="item.streaming" class="chat-cursor">▍</span></div> <div v-if="item.role === 'assistant'" class="chat-content">
<div class="chat-markdown" v-html="markdownRenderer.render(item.content || '')" />
<span v-if="item.streaming" class="chat-cursor">▍</span>
</div>
<div v-else class="chat-content">{{ item.content }}</div>
<div v-if="item.sources && item.sources.length && !item.streaming" class="chat-sources"> <div v-if="item.sources && item.sources.length && !item.streaming" class="chat-sources">
<div class="source-title">参考切片{{ item.llmUsed ? '(已调用大模型)' : '(未配置大模型,直接返回检索结果)' }}</div> <div class="source-title">参考切片{{ item.llmUsed ? '(已调用大模型)' : '(未配置大模型,直接返回检索结果)' }}</div>
<el-collapse> <el-collapse>
...@@ -212,12 +222,73 @@ loadOptions() ...@@ -212,12 +222,73 @@ loadOptions()
background: #fff; background: #fff;
border-radius: 8px; border-radius: 8px;
padding: 10px 12px; padding: 10px 12px;
white-space: pre-wrap;
line-height: 1.6; line-height: 1.6;
} }
.chat-item.user .chat-content { .chat-item.user .chat-content {
background: #409eff; background: #409eff;
color: #fff; color: #fff;
white-space: pre-wrap;
}
.chat-markdown :deep(p) {
margin: 0 0 8px;
}
.chat-markdown :deep(p:last-child),
.chat-markdown :deep(ul:last-child),
.chat-markdown :deep(ol:last-child),
.chat-markdown :deep(pre:last-child),
.chat-markdown :deep(blockquote:last-child),
.chat-markdown :deep(table:last-child) {
margin-bottom: 0;
}
.chat-markdown :deep(h1),
.chat-markdown :deep(h2),
.chat-markdown :deep(h3),
.chat-markdown :deep(h4),
.chat-markdown :deep(h5),
.chat-markdown :deep(h6) {
margin: 12px 0 8px;
line-height: 1.4;
}
.chat-markdown :deep(ul),
.chat-markdown :deep(ol) {
padding-left: 24px;
margin: 0 0 8px;
}
.chat-markdown :deep(blockquote) {
border-left: 3px solid #dcdfe6;
color: #606266;
margin: 8px 0;
padding-left: 12px;
}
.chat-markdown :deep(pre) {
background: #f5f7fa;
border-radius: 4px;
margin: 8px 0;
overflow-x: auto;
padding: 10px 12px;
}
.chat-markdown :deep(code) {
background: #f5f7fa;
border-radius: 3px;
font-family: Consolas, Monaco, monospace;
padding: 2px 4px;
}
.chat-markdown :deep(pre code) {
background: transparent;
padding: 0;
}
.chat-markdown :deep(table) {
border-collapse: collapse;
display: block;
margin: 8px 0;
overflow-x: auto;
width: max-content;
max-width: 100%;
}
.chat-markdown :deep(th),
.chat-markdown :deep(td) {
border: 1px solid #dcdfe6;
padding: 6px 10px;
} }
.chat-cursor { .chat-cursor {
display: inline-block; display: inline-block;
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论