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"
},
"dependencies": {
"@crazydos/vue-markdown": "^1.1.4",
"@vue-flow/core": "^1.48.2",
"axios": "^1.3.6",
"crypto-js": "^4.2.0",
@@ -21,12 +22,12 @@
"element-plus": "^2.13.2",
"fabric-with-all": "^5.3.1",
"gsap": "^3.13.0",
"markdown-it": "^14.1.0",
"md5": "^2.3.0",
"normalize.css": "^8.0.1",
"pinia": "^2.0.32",
"pinia-persistedstate-plugin": "^0.1.0",
"pinia-plugin-persistedstate": "^3.1.0",
"rehype-raw": "^7.0.0",
"three": "^0.148.0",
"vue": "^3.2.47",
"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: {
copySuccess: 'Text copied to clipboard',
copyFaild: 'Copy failed. Your browser may be restricting clipboard access. Please try copying manually.',
Download: 'Download'
Download: 'Download',
deleteSuccess: 'Successfully deleted'
},
// Version Tree

View File

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

View File

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

View File

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

View File

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