diff --git a/src/assets/icons/threeLogo.svg b/src/assets/icons/threeLogo.svg new file mode 100644 index 0000000..8e3c6b2 --- /dev/null +++ b/src/assets/icons/threeLogo.svg @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/images/threeLoading.png b/src/assets/images/threeLoading.png new file mode 100644 index 0000000..512121d Binary files /dev/null and b/src/assets/images/threeLoading.png differ diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue b/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue index cbf4fb6..6334d86 100644 --- a/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue +++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue @@ -166,9 +166,15 @@ defineExpose({open})
+
+ +
-
Load...
+
+ + {{ $t('threeModel.loading') }} +
@@ -190,30 +196,49 @@ defineExpose({open}) border: 1px solid #D9D9D9; overflow: hidden; } + > .icon{ + position: absolute; + bottom: 2.4rem; + right: 2.4rem; + } > .load{ position: absolute; width: 100%; height: 100%; top: 0; left: 0; - background: rgba(0, 0, 0, .2); + background: #e6e6e6; display: flex; align-items: center; justify-content: center; flex-direction: column; color: #fff; + > .text{ + font-weight: 500; + font-size: 1.8rem; + line-height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: #747474; + > img{ + margin-right: 1rem; + } + } > i{ font-size: 3rem; } > .loadBox{ - width: 15rem; + width: 26rem; height: 1rem; - border-radius: 1rem; + border-radius: 3.3rem; background: #fff; overflow: hidden; + margin-top: 1.2rem; > .schedule{ height: 100%; - background: greenyellow; + border-radius: 3.3rem; + background: #848484; } } } diff --git a/src/components/Canvas/FlowCanvas/components/tools/upload-file.vue b/src/components/Canvas/FlowCanvas/components/tools/upload-file.vue index 27a6907..740e719 100644 --- a/src/components/Canvas/FlowCanvas/components/tools/upload-file.vue +++ b/src/components/Canvas/FlowCanvas/components/tools/upload-file.vue @@ -80,7 +80,7 @@ text-align: center; line-height: 18px; background-color: #fff; - font-size: 6px; + font-size: 9px; color: #000; border: 1px solid #d9d9d9; cursor: pointer; diff --git a/src/lang/en.ts b/src/lang/en.ts index c6a61b2..1750c74 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -187,5 +187,9 @@ export default { }, assistant: { inputPlaceholder: 'Ask anything', + }, + //3d面板 + threeModel: { + loading: 'Loading', } } diff --git a/src/lang/zh-cn.ts b/src/lang/zh-cn.ts index b98be7b..db123b9 100644 --- a/src/lang/zh-cn.ts +++ b/src/lang/zh-cn.ts @@ -182,5 +182,9 @@ export default { }, assistant: { inputPlaceholder: '请输入' + }, + //3d面板 + threeModel: { + loading: '加载中', } } diff --git a/src/views/home/agent/components/Agent.vue b/src/views/home/agent/components/Agent.vue index c075d81..f654e30 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 @@ -143,6 +146,7 @@ id: messageList.value.length + 1, text: '', isUser: false, + sessionId: 'projectStore.state.id', loading: true, thinking: false, thinkingText: '', @@ -206,11 +210,13 @@ // 流式响应处理 let contentBody = '' let buffer = '' + const webAddressList = [] const reader = response.body?.getReader() if (!reader) throw new Error('无法获取流读取器') const decoder = new TextDecoder() - + let previousEventName = '' // 记录上一个事件名称 + let hasReportStarted = false // 标记 report 是否已经开始 try { let flag = true while (flag) { @@ -232,16 +238,41 @@ 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_' + projectStore.state.id, + reportsContent.value + ) + } + + previousEventName = eventName + + // console.log('eventName:', eventName, 'event:', event) + + // 根据事件名称精确判断,而不是用 includes + if (eventName === 'error') { aiMessage.text = '出现错误,请重试' aiMessage.streaming = false aiMessage.loading = false @@ -249,27 +280,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 +311,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.report + } 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 +369,9 @@ console.warn('⚠️ JSON 格式错误,跳过:', jsonText) } } + + // 更新上一个事件名称 + previousEventName = eventName } } } catch (error) { @@ -378,7 +431,7 @@ } // 处理对话列表,将连续的 assistant 消息合并为一条 - const processDialogue = (dialogue, startIndex, existingImgList) => { + const processDialogue = (dialogue, startIndex, existingImgList, sessionId) => { if (!dialogue || dialogue.length === 0) return [] const result = [] @@ -393,26 +446,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 +479,8 @@ ...item, text: item.content, isUser: item.role === 'user', - id: result.length + 1 + id: result.length + 1, + sessionId: sessionId }) i++ } @@ -430,6 +488,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 +550,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" />
-
+
-