feat: 修改markdown格式化&实时对话sketch

This commit is contained in:
2026-03-12 16:54:54 +08:00
parent 302d7247a5
commit 13a31d584b
11 changed files with 1841 additions and 231 deletions

1835
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@crazydos/vue-markdown": "^1.1.4",
"@vue-flow/core": "^1.48.2", "@vue-flow/core": "^1.48.2",
"axios": "^1.3.6", "axios": "^1.3.6",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
@@ -21,12 +22,12 @@
"element-plus": "^2.13.2", "element-plus": "^2.13.2",
"fabric-with-all": "^5.3.1", "fabric-with-all": "^5.3.1",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"markdown-it": "^14.1.0",
"md5": "^2.3.0", "md5": "^2.3.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pinia": "^2.0.32", "pinia": "^2.0.32",
"pinia-persistedstate-plugin": "^0.1.0", "pinia-persistedstate-plugin": "^0.1.0",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
"rehype-raw": "^7.0.0",
"three": "^0.148.0", "three": "^0.148.0",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-draggable-plus": "^0.6.1", "vue-draggable-plus": "^0.6.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -150,7 +150,8 @@ export default {
agent: { agent: {
copySuccess: 'Text copied to clipboard', copySuccess: 'Text copied to clipboard',
copyFaild: 'Copy failed. Your browser may be restricting clipboard access. Please try copying manually.', copyFaild: 'Copy failed. Your browser may be restricting clipboard access. Please try copying manually.',
Download: 'Download' Download: 'Download',
deleteSuccess: 'Successfully deleted'
}, },
// Version Tree // Version Tree

View File

@@ -145,7 +145,8 @@ export default {
copySuccess: '文本已复制到剪贴板', copySuccess: '文本已复制到剪贴板',
copyFaild: copyFaild:
'复制失败。您的浏览器可能限制了剪贴板访问,请允许浏览器访问剪贴板或尝试手动复制。', '复制失败。您的浏览器可能限制了剪贴板访问,请允许浏览器访问剪贴板或尝试手动复制。',
Download: '下载' Download: '下载',
deleteSuccess:'删除成功'
}, },
// Version Tree // Version Tree
@@ -180,6 +181,6 @@ export default {
cancel: '取消' cancel: '取消'
}, },
assistant: { assistant: {
inputPlaceholder: '请输入', inputPlaceholder: '请输入'
} }
} }

View File

@@ -252,17 +252,22 @@
if (event.includes('todo') || event.includes('webAddress')) { if (event.includes('todo') || event.includes('webAddress')) {
break break
} }
let hasSketch = false
if (event.includes('sketchIDAndUrl')) {
hasSketch = true
}
const dataLines = event const dataLines = event
.split(/\n/) .split(/\n/)
.filter((line) => line.startsWith('data:')) .filter((line) => line.startsWith('data:'))
.map((line) => line.replace(/^data:\s*/, '').trim()) .map((line) => line.replace(/^data:\s*/, '').trim())
.filter((content) => content.startsWith('{') || content.startsWith('[')) .filter((content) => content.startsWith('{') || content.startsWith('['))
// console.log('dataLInes', dataLines) // console.log('dataLInes', dataLines)
if (isNodeIdEvent) { if (isNodeIdEvent) {
params.versionID = dataLines[0] params.versionID = dataLines[0]
projectStore.setProject({ nodeId: dataLines[0] }) projectStore.setProject({ nodeId: dataLines[0] })
} }
if (event.includes('tool')) { if (event.includes('tool')) {
MyEvent.emit('loading-sketch') MyEvent.emit('loading-sketch')
} }
@@ -274,11 +279,10 @@
const jsonData = JSON.parse(jsonText) const jsonData = JSON.parse(jsonText)
console.log('jsonData', jsonData) console.log('jsonData', jsonData)
// 赋值 project_id 和 version_id if (hasSketch) {
// if (jsonData.project_id) params.projectID = jsonData.project_id sketchList.value.push({
// if (jsonData.version_id) params.versionID = jsonData.version_id [Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
if (jsonData.image_url) { })
sketchList.value.push(jsonData.image_url)
} }
if ( if (
jsonData.content && jsonData.content &&
@@ -382,10 +386,6 @@
while (i < dialogue.length) { while (i < dialogue.length) {
const item = dialogue[i] const item = dialogue[i]
// if (item.image_url) {
// existingImgList.push(item.image_url)
// }
if (item.role === 'user') { if (item.role === 'user') {
// user 角色直接添加 // user 角色直接添加
result.push({ result.push({
@@ -402,9 +402,6 @@
// 继续往后找连续的 assistant 消息 // 继续往后找连续的 assistant 消息
let j = i + 1 let j = i + 1
while (j < dialogue.length && dialogue[j].role === 'assistant') { while (j < dialogue.length && dialogue[j].role === 'assistant') {
// if (dialogue[j].image_url) {
// existingImgList.push(dialogue[j].image_url)
// }
combinedContent += dialogue[j].content || '' combinedContent += dialogue[j].content || ''
j++ j++
} }

View File

@@ -19,8 +19,17 @@
class="img-item" class="img-item"
/> />
</div> </div>
<div class="message-txt markdown-body"> <div class="message-txt markdown-body flex flex-col">
<div v-html="formatMessage"></div> <!-- <div v-html="formatMessage"></div> -->
<VueMarkdown
:custom-attrs="customAttrs"
:markdown="content.text"
:rehype-plugins="[rehypeRaw]"
>
<template v-slot:s-ReportCard="" {children:children,...attrs}>
<ReportCard :report="{ title: attrs.title, content: attrs.content }" />
</template>
</VueMarkdown>
</div> </div>
<div class="operate flex" :class="{ 'is-user': content.isUser }"> <div class="operate flex" :class="{ 'is-user': content.isUser }">
<template v-if="content.isUser"> <template v-if="content.isUser">
@@ -59,11 +68,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="message-context" v-show="content.streaming">
<div class="message-txt">
<div v-html="formatMessage"></div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -74,9 +78,11 @@
import gsap from 'gsap' import gsap from 'gsap'
import userThumb from '@/assets/images/user-thumb.jpg' import userThumb from '@/assets/images/user-thumb.jpg'
import agentThumb from '@/assets/images/agent-thumb.jpg' import agentThumb from '@/assets/images/agent-thumb.jpg'
import markdownIt from 'markdown-it' import ReportCard from './ReportCard.vue'
import UrlCard from './UrlCard.vue'
const md = new markdownIt() import { VueMarkdown } from '@crazydos/vue-markdown'
import type { CustomAttrs } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw'
const { t } = useI18n() const { t } = useI18n()
@@ -85,6 +91,8 @@
isLast: Boolean isLast: Boolean
}>() }>()
const emit = defineEmits(['regenerate'])
const imageList = computed(() => { const imageList = computed(() => {
const { imageUrls } = props.content const { imageUrls } = props.content
const list = [] const list = []
@@ -99,12 +107,18 @@
return list return list
}) })
const formatMessage = computed(() => { const customAttrs: CustomAttrs = {
const str = md.render(props.content.text) img: {
return str style: 'max-width: 100%;'
}) },
a: (node, combinedAttrs) => {
const emit = defineEmits(['regenerate']) if (typeof node.properties.href === 'string') {
return { target: '_blank', rel: 'noopener noreferrer' }
} else {
return {}
}
}
}
const operateList = ref([ const operateList = ref([
{ {
@@ -219,7 +233,7 @@
.message-context { .message-context {
line-height: 2rem; line-height: 2rem;
font-size: 1.4rem; font-size: 1.4rem;
max-width: 82%; width: 82%;
} }
} }
.operate { .operate {
@@ -278,5 +292,8 @@
code { code {
white-space: pre-wrap; white-space: pre-wrap;
} }
img {
max-width: 100%;
}
} }
</style> </style>

View File

@@ -64,84 +64,7 @@
<span>{{ $t('agent.Download') }}</span> <span>{{ $t('agent.Download') }}</span>
</div> </div>
</div> </div>
<div class="content"> <div class="content"></div>
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题 <br />
# 一级标题
<br />
# 一级标题
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -158,7 +81,8 @@
import { useProjectStore } from '@/stores' import { useProjectStore } from '@/stores'
const projectStore = useProjectStore() const projectStore = useProjectStore()
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const emits = defineEmits(['deleteSketch']) const emits = defineEmits(['deleteSketch'])
// 存储每个图片的加载状态 // 存储每个图片的加载状态
@@ -217,7 +141,7 @@
versionNodeId: projectStore.state.nodeId versionNodeId: projectStore.state.nodeId
}).then((res) => { }).then((res) => {
if (res) { if (res) {
ElMessage.success('Delete success') ElMessage.success(t('agent.deleteSuccess'))
emits('deleteSketch', Object.keys(item)[0]) emits('deleteSketch', Object.keys(item)[0])
} }
}) })
@@ -230,8 +154,6 @@
watch( watch(
() => props.sketchList, () => props.sketchList,
(val) => { (val) => {
console.log('-sketchList-', val)
if (val.length > 0) { if (val.length > 0) {
showLoading.value = false showLoading.value = false
} }

View File

@@ -0,0 +1,48 @@
<template>
<div class="report-card">
<div class="report-card-header">
<span>{{ report.title }}</span>
</div>
<div class="report-card-content">
<span>{{ report.content }}</span>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
report: any
}>()
</script>
<style lang="less" scoped>
.report-card {
width:100%;
margin: 2.4rem 0;
min-height: 11.2rem;
background: url('@/assets/images/report-card.png') no-repeat;
background-size: 100% 100%;
padding: 2.9rem;
overflow: hidden;
&:first-of-type{
margin-top: 0;
}
&-header {
font-family: 'Medium';
font-size: 1.6rem;
margin-bottom: 1.3rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
line-clamp: 3;
}
&-content{
font-family: 'Regular';
font-weight: 300;
font-size: 1.6rem;
color: #7c7c7c;
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<ReportCard :report="{title: 'WebSources', content: 'Destination URL'}"/>
</template>
<script setup lang="ts">
import ReportCard from './ReportCard.vue'
</script>
<style lang="less" scoped>
</style>