diff --git a/src/views/home/agent/components/Agent.vue b/src/views/home/agent/components/Agent.vue
index c075d81..3ae97ad 100644
--- a/src/views/home/agent/components/Agent.vue
+++ b/src/views/home/agent/components/Agent.vue
@@ -33,7 +33,10 @@
const agentStore = useAgentStore()
const projectStore = useProjectStore()
- const emits = defineEmits(['update:sketchList'])
+ const reportsContent = ref('')
+ const isGeneratingReport = ref(false)
+
+ const emits = defineEmits(['update:sketchList', 'setTitle'])
const props = withDefaults(
defineProps<{
title: string
@@ -206,6 +209,7 @@
// 流式响应处理
let contentBody = ''
let buffer = ''
+ const webAddressList = []
const reader = response.body?.getReader()
if (!reader) throw new Error('无法获取流读取器')
@@ -230,18 +234,43 @@
let events = buffer.split(/\n\n/)
buffer = events.pop() // 保留不完整块
+ let previousEventName = '' // 记录上一个事件名称
+ let hasReportStarted = false // 标记 report 是否已经开始
+
for (let event of events) {
if (!event.trim()) continue
- // debugger
- // 过滤掉 id: 等字段,只取 data:
- let isNodeIdEvent = false
- if (event.includes('nodeId')) {
- isNodeIdEvent = true
- // continue
+ // 解析事件名称(从 event:xxx 行)
+ const eventName =
+ event
+ .split(/\n/)
+ .find((line) => line.startsWith('event:'))
+ ?.replace(/^event:\s*/, '')
+ ?.trim() || ''
+
+ if (!hasReportStarted && eventName === 'report') {
+ console.log('开始生成报告--------')
+
+ isGeneratingReport.value = true
+ contentBody += `123`
+ hasReportStarted = true
}
- console.log('event', event)
- if (event.includes('error')) {
+
+ if (
+ previousEventName === 'report' &&
+ eventName !== 'report' &&
+ reportsContent.value
+ ) {
+ isGeneratingReport.value = false
+ localStorage.setItem('reportsContent', reportsContent.value)
+ }
+
+ previousEventName = eventName
+
+ // console.log('eventName:', eventName, 'event:', event)
+
+ // 根据事件名称精确判断,而不是用 includes
+ if (eventName === 'error') {
aiMessage.text = '出现错误,请重试'
aiMessage.streaming = false
aiMessage.loading = false
@@ -249,27 +278,29 @@
flag = false
break
}
- // TODO: 暂时不处理webAddress
- if (event.includes('todo')) {
+ if (eventName === 'todo') {
break
}
- let hasSketch = false
- if (event.includes('sketchIDAndUrl')) {
- hasSketch = true
- }
+
+ let isNodeIdEvent = eventName === 'nodeId'
+
+ let hasSketch = eventName === 'sketch'
const dataLines = event
.split(/\n/)
.filter((line) => line.startsWith('data:'))
- .map((line) => line.replace(/^data:\s*/, '').trim())
+ .map((line) => line.replace(/^data:\s*/, ''))
.filter((content) => content.startsWith('{') || content.startsWith('['))
- console.log('dataLInes', dataLines)
+ // console.log('dataLInes', dataLines)
if (isNodeIdEvent) {
params.versionID = dataLines[0]
projectStore.setProject({ nodeId: dataLines[0] })
}
+ if (eventName === 'webAddress') {
+ console.log('webAddress-----', eventName, dataLines)
+ }
- if (event.includes('tool')) {
+ if (eventName === 'tool') {
MyEvent.emit('loading-sketch')
}
@@ -278,21 +309,38 @@
try {
const jsonData = JSON.parse(jsonText)
- console.log('jsonData', jsonData)
+ // console.log('jsonData', jsonData)
+ if (jsonData.webAddress) {
+ console.log('webAddress-----', jsonData)
+ }
+ if (jsonData.title) {
+ emits('setTitle', jsonData.title)
+ }
if (hasSketch) {
sketchList.value.push({
[Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
})
}
- if (
- jsonData.content &&
- jsonData.content.length > 0 &&
- jsonData.type !== 'end'
- ) {
- contentBody += jsonData.content
- aiMessage.text = contentBody
- aiMessage.loading = false
+ if (eventName === 'report') {
+ reportsContent.value += jsonData.content
+ } else {
+ if (jsonData.reasoning) {
+ aiMessage.thinking = true
+ aiMessage.loading = false
+ aiMessage.thinkingText += jsonData.reasoning
+ } else {
+ aiMessage.thinking = false
+ if (
+ jsonData.content &&
+ jsonData.content.length > 0 &&
+ jsonData.type !== 'end'
+ ) {
+ contentBody += jsonData.content
+ aiMessage.text = contentBody
+ aiMessage.loading = false
+ }
+ }
}
if (jsonData.type === 'end') {
aiMessage.streaming = false
@@ -319,6 +367,9 @@
console.warn('⚠️ JSON 格式错误,跳过:', jsonText)
}
}
+
+ // 更新上一个事件名称
+ previousEventName = eventName
}
}
} catch (error) {
@@ -378,7 +429,7 @@
}
// 处理对话列表,将连续的 assistant 消息合并为一条
- const processDialogue = (dialogue, startIndex, existingImgList) => {
+ const processDialogue = (dialogue, startIndex, existingImgList, sessionId) => {
if (!dialogue || dialogue.length === 0) return []
const result = []
@@ -393,26 +444,30 @@
...item,
text: item.content,
isUser: true,
- id: result.length + 1
+ id: result.length + 1,
+ sessionId: sessionId
})
i++
} else if (item.role === 'assistant') {
// assistant 角色,拼接直到下一个 user
let combinedContent = item.content || ''
-
+ let combinedThinkingText = item.reasoning || ''
// 继续往后找连续的 assistant 消息
let j = i + 1
while (j < dialogue.length && dialogue[j].role === 'assistant') {
combinedContent += dialogue[j].content || ''
+ combinedThinkingText += dialogue[j].reasoning || ''
j++
}
result.push({
...item,
content: combinedContent,
+ thinkingText: combinedThinkingText,
text: combinedContent,
isUser: false,
- id: result.length + 1
+ id: result.length + 1,
+ sessionId: sessionId
})
i = j
@@ -422,7 +477,8 @@
...item,
text: item.content,
isUser: item.role === 'user',
- id: result.length + 1
+ id: result.length + 1,
+ sessionId: sessionId
})
i++
}
@@ -430,6 +486,44 @@
return result
}
+ const processSession = (session, imgList, ancestorsList, idCounterRef) => {
+ if (!session) return
+
+ // 1. 在 dialogue 第一个 report 有值的项的 content 中插入报告卡片
+ if (session.dialogue) {
+ for (let i = 0; i < session.dialogue.length; i++) {
+ if (session.dialogue[i].report) {
+ session.dialogue[i].content =
+ `123` +
+ (session.dialogue[i].content || '')
+ break
+ }
+ }
+ }
+
+ // 2. 收集 report 内容并保存到 localStorage
+ let reportStr = ''
+ session.dialogue?.forEach((item) => {
+ if (item.report) {
+ reportStr += item.report
+ }
+ })
+ if (reportStr && session.id) {
+ localStorage.setItem(`reportsContent_${session.id}`, reportStr)
+ }
+
+ // 3. 收集 sketchIDAndUrl 到 imgList
+ if (session.sketchIDAndUrl) {
+ imgList.push(...session.sketchIDAndUrl)
+ }
+
+ // 4. 处理 dialogue
+ const list = processDialogue(session.dialogue, 0, imgList, session.id)
+ list.forEach((el) => {
+ el.id = idCounterRef.value++
+ })
+ ancestorsList.push(...list)
+ }
const setChatInfo = (info) => {
const initialData = agentStore.getInitialProjectData
@@ -454,37 +548,25 @@
messageList.value = []
return
}
-
const { ancestors, current } = data
- let imgList = []
- const ancestorsList = []
- let ancestorsIdCounter = 1
- if (ancestors) {
- ancestors.forEach((item) => {
- if (item.sketchIDAndUrl) {
- imgList = imgList.concat(item.sketchIDAndUrl)
- }
- const list = processDialogue(item.dialogue, 0, imgList)
- // 重新设置 id
- list.forEach((el) => {
- el.id = ancestorsIdCounter++
- })
- ancestorsList.push(...list)
- })
- }
- const currentList = processDialogue(current?.dialogue, 0, imgList)
- if (current.sketchIDAndUrl) {
- imgList = imgList.concat(current.sketchIDAndUrl)
- }
+ // 处理单个会话(ancestor 或 current)
- currentList.forEach((el, index) => {
- el.id = index + 1 + ancestorsList.length
+ const imgList = []
+ const ancestorsList = []
+ const idCounterRef = { value: 1 }
+
+ // 处理所有 ancestors
+ ancestors?.forEach((item) => {
+ processSession(item, imgList, ancestorsList, idCounterRef)
})
+ // 处理 current
+ processSession(current, imgList, ancestorsList, idCounterRef)
+
// 延迟设置新数据,确保 UI 有时间响应清空操作
nextTick(() => {
- messageList.value = [...ancestorsList, ...currentList]
+ messageList.value = [...ancestorsList]
params.versionID = current?.id
sketchList.value = imgList
})
diff --git a/src/views/home/agent/components/Item.vue b/src/views/home/agent/components/Item.vue
index e604da0..fa08495 100644
--- a/src/views/home/agent/components/Item.vue
+++ b/src/views/home/agent/components/Item.vue
@@ -16,7 +16,7 @@
class="img-item"
/>
-
+
-
+
@@ -81,11 +82,12 @@
import gsap from 'gsap'
import userThumb from '@/assets/images/user-thumb.jpg'
import agentThumb from '@/assets/images/agent-thumb.png'
- import ReportCard from './ReportCard.vue'
- import UrlCard from './UrlCard.vue'
+ import Card from './ReportCard.vue'
+ import Url from './UrlCard.vue'
import { VueMarkdown } from '@crazydos/vue-markdown'
import type { CustomAttrs } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw'
+ import MyEvent from '@/utils/myEvent'
const { t } = useI18n()
@@ -94,6 +96,14 @@
isLast: Boolean
}>()
+ watch(
+ () => props.content,
+ (newVal) => {
+ console.log('newVal-----', newVal)
+ },
+ { immediate: true }
+ )
+
const emit = defineEmits(['regenerate'])
const imageList = computed(() => {
@@ -200,7 +210,8 @@
props.content.thinkingCollapsed = !props.content.thinkingCollapsed
}
- const handleClickReport = (data) => {
+ const handleClickReport = () => {
+ MyEvent.emit('openReport', props.content.sessionId)
// 点击显示报告
}
const handleClickUrls = (data) => {
diff --git a/src/views/home/agent/components/Preview.vue b/src/views/home/agent/components/Preview.vue
index 0b722d7..b510bf2 100644
--- a/src/views/home/agent/components/Preview.vue
+++ b/src/views/home/agent/components/Preview.vue
@@ -57,14 +57,21 @@
-
+
{{ $t('agent.Download') }}
-
+
+
+
+
@@ -82,6 +89,10 @@
const projectStore = useProjectStore()
import MyEvent from '@/utils/myEvent'
import { useI18n } from 'vue-i18n'
+ import { VueMarkdown } from '@crazydos/vue-markdown'
+ import type { CustomAttrs } from '@crazydos/vue-markdown'
+ import rehypeRaw from 'rehype-raw'
+
const { t } = useI18n()
const emits = defineEmits(['deleteSketch'])
@@ -99,6 +110,36 @@
}
)
+ const customAttrs: CustomAttrs = {
+ img: {
+ style: 'max-width: 100%;display:block;'
+ },
+ a: (node, combinedAttrs) => {
+ if (typeof node.properties.href === 'string') {
+ return { target: '_blank', rel: 'noopener noreferrer' }
+ } else {
+ return {}
+ }
+ }
+ }
+
+ const sessionId = ref('')
+ const markdownContent = ref('')
+const setSessionId = (id: string) => {
+
+ sessionId.value = id
+ }
+ watch(
+ () => sessionId.value,
+ (newVal) => {
+ if (newVal) {
+ markdownContent.value = localStorage.getItem(`reportsContent_${newVal}`)
+ console.log('markdownContent-----', markdownContent.value);
+
+ }
+ }
+ )
+
// 图片加载完成时触发
const handleImageLoad = (index: number) => {
loadedStatus[index] = true
@@ -147,6 +188,20 @@
})
}
+ const handleDownloadMd = () => {
+ if (!markdownContent.value) return
+
+ const blob = new Blob([markdownContent.value], { type: 'text/markdown' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `report-${Date.now()}.md`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+ }
+
const showLoading = ref(false)
const handleLoadingSketch = () => {
showLoading.value = true
@@ -166,6 +221,10 @@
onUnmounted(() => {
MyEvent.remove('loading-sketch', handleLoadingSketch)
})
+
+ defineExpose({
+ setSessionId
+ })