This commit is contained in:
lzp
2026-03-25 11:27:26 +08:00
26 changed files with 212 additions and 122 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -76,7 +76,6 @@
tier: props.node.data?.tier, tier: props.node.data?.tier,
superiorID, superiorID,
isActive: props.node.data?.isActive, isActive: props.node.data?.isActive,
originalImage: props.node.data?.originalImage,
secondaryMenu: v.secondaryMenu, secondaryMenu: v.secondaryMenu,
createIndexPosition: props.node.data.createIndexPosition, createIndexPosition: props.node.data.createIndexPosition,
} }
@@ -89,11 +88,10 @@
superiorID, superiorID,
isActive: props.node.data?.isActive, isActive: props.node.data?.isActive,
createIndexPosition: props.node.data.createIndexPosition, createIndexPosition: props.node.data.createIndexPosition,
originalImage: props.node.data?.originalImage,
} }
}) })
} }
stateManager.deleteNode(id) // stateManager.deleteNode(id)
} }
defineExpose({}) defineExpose({})

View File

@@ -50,7 +50,7 @@
]) ])
const getApiData = ()=>{ const getApiData = ()=>{
return { return {
variantCount: '2', variantCount: '4',
colors: data.colors, colors: data.colors,
mode: data.mode, mode: data.mode,
} }

View File

@@ -9,24 +9,26 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, inject, useAttrs } from 'vue' import { reactive, inject, useAttrs, computed } from 'vue'
import myEvent from '@/utils/myEvent' import myEvent from '@/utils/myEvent'
import { getCurrentTime } from '../../../../tools/tools.ts' import { getCurrentTime } from '../../../../tools/tools.ts'
import { NODE_DATATIER } from '../../../tools/index.d' import { NODE_DATATIER } from '../../../tools/index.d'
const attrs = useAttrs() const attrs = useAttrs()
const data = reactive({
url: attrs.node?.data?.originalImage,
})
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
const nodeManager = inject('nodeManager') as any const nodeManager = inject('nodeManager') as any
const eventManager = inject('eventManager') as any const eventManager = inject('eventManager') as any
const data = reactive({
url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
})
const getApiData = ()=>{ const getApiData = ()=>{
return { return {
} }
} }
const opCanvas = ()=>{ const opCanvas = ()=>{
const superiorNodeUrl = stateManager.getSuperiorNodeImage(attrs?.node?.data?.superiorID || null)
if (!superiorNodeUrl) console.log('superiorNodeUrl 找不到原始图片')
const data = { const data = {
url:attrs?.node?.data?.originalImage, url:superiorNodeUrl,
canvasId: attrs?.node?.data?.canvasId || null, canvasId: attrs?.node?.data?.canvasId || null,
sketchId: stateManager.sketchId.value, sketchId: stateManager.sketchId.value,
onWorkbench:(options)=>{ onWorkbench:(options)=>{

View File

@@ -42,12 +42,15 @@
import ColorPalette from './color-palette.vue' import ColorPalette from './color-palette.vue'
import To3View from './to-3view.vue' import To3View from './to-3view.vue'
import To3DModel from './to-3d-model.vue' import To3DModel from './to-3d-model.vue'
import { useI18n } from 'vue-i18n'
import { ElMessageBox } from 'element-plus'
import { toRealStyleApi, toColorPaletteApi, toSceneCompositionApi, sketchAddPrintApi, sketchToThreeApi, threeToThreeViewsApi } from '@/api/flow-canvas' import { toRealStyleApi, toColorPaletteApi, toSceneCompositionApi, sketchAddPrintApi, sketchToThreeApi, threeToThreeViewsApi } from '@/api/flow-canvas'
// import ToVideo from './to-video.vue' // import ToVideo from './to-video.vue'
// import AddPrint from './add-print.vue' // import AddPrint from './add-print.vue'
// import ToCAD from './to-cad.vue' // import ToCAD from './to-cad.vue'
const { t } = useI18n()
const attrs = useAttrs() const attrs = useAttrs()
const componentRef = ref(null) const componentRef = ref(null)
const components = [ const components = [
@@ -144,12 +147,12 @@
const onGenerateClick = async () => { const onGenerateClick = async () => {
const data = componentRef.value?.getApiData?.() || {} const data = componentRef.value?.getApiData?.() || {}
const subordNodes = stateManager.getSubordNodes(attrs.node.id) const subordNodes = stateManager.getSubordNodes(attrs.node.id)
const superiorNodeUrl = stateManager.getSuperiorNodeImage(attrs.node.data.superiorID)
if(!superiorNodeUrl)return console.log('superiorNodeUrl 找不到原始图片')
emit('update-data', componentRef.value?.data) emit('update-data', componentRef.value?.data)
if(!attrs.node?.data?.originalImage)console.log('originalImage 找不到原始图片')
const apiData = { const apiData = {
sketchId: props.sketchId, sketchId: props.sketchId,
imageUrl: attrs.node?.data?.originalImage, imageUrl: superiorNodeUrl,
...data, ...data,
} }
const taskList = await currentComponent.value.api(apiData).then((rv)=>{ const taskList = await currentComponent.value.api(apiData).then((rv)=>{
@@ -177,8 +180,9 @@
}) })
} }
//删除功能卡片 //删除功能卡片
const onDeleteClick = ()=>{ const onDeleteClick = async ()=>{
stateManager.deleteNode(attrs.node.id,{isElMessageBox:true}) console.log(stateManager.nodes)
stateManager.getSubordinateAllNodes(attrs.node.id,{ isElMessageBox: true })
} }
const setDate = () => { const setDate = () => {
for (const key in props.data) { for (const key in props.data) {

View File

@@ -9,12 +9,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, useAttrs } from 'vue' import { reactive, inject, useAttrs, computed } from 'vue'
import uploadFile from '../../tools/upload-file.vue' import uploadFile from '../../tools/upload-file.vue'
const attrs = useAttrs() const attrs = useAttrs()
const stateManager = inject('stateManager') as any
const data = reactive({ const data = reactive({
url: attrs.node?.data?.originalImage, url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
}) })
const getApiData = ()=>{ const getApiData = ()=>{
return { return {

View File

@@ -13,8 +13,8 @@
const attrs = useAttrs() const attrs = useAttrs()
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
const data = reactive({ const data = reactive({
url: attrs.node.data.originalImage, url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
}) })
const getApiData = ()=>{ const getApiData = ()=>{
let glbUrl = null let glbUrl = null
const superiorNode = stateManager.nodes.value.filter((item:any)=>item.id === attrs.node?.data?.superiorID)[0] const superiorNode = stateManager.nodes.value.filter((item:any)=>item.id === attrs.node?.data?.superiorID)[0]

View File

@@ -253,18 +253,11 @@
) )
const onAdd = () => { const onAdd = () => {
const tier_ = tier.value + 1 const tier_ = tier.value + 1
// 从data中获取originalImage
let nodeData = props.node?.data?.data.imageProcessTasks.filter(
(v) => v.taskId === props.node?.data?.data.selectTaskId
)
const subordNodes = stateManager.getSubordNodes(props.node.id) const subordNodes = stateManager.getSubordNodes(props.node.id)
const originalImage = nodeData[0]?.url
if (!originalImage) console.log('originalImage 找不到原始图片')
stateManager.nodeManager.createCardsSelect({ stateManager.nodeManager.createCardsSelect({
data: { data: {
tier: tier_, tier: tier_,
superiorID: props.node.id, superiorID: props.node.id,
originalImage,
createIndexPosition: subordNodes.length + 1, createIndexPosition: subordNodes.length + 1,
isActive: subordNodes.length == 0 isActive: subordNodes.length == 0
} }

View File

@@ -94,7 +94,7 @@ export class EventManager {
handleDelete(event: any, activeNodeID: string) { handleDelete(event: any, activeNodeID: string) {
event.preventDefault() event.preventDefault()
if (!activeNodeID) return console.warn('没有选中节点') if (!activeNodeID) return console.warn('没有选中节点')
this.stateManager.deleteNode(activeNodeID, { isElMessageBox: true }) this.stateManager.getSubordinateAllNodes(activeNodeID, { isElMessageBox: true })
} }
/** 处理键盘事件 */ /** 处理键盘事件 */
_handleKeyDown: any _handleKeyDown: any

View File

@@ -10,7 +10,6 @@ interface NodeData {
superiorNodeType?: string// 上级节点类型 superiorNodeType?: string// 上级节点类型
disableDelete?: boolean// 是否禁用删除 disableDelete?: boolean// 是否禁用删除
disableCopy?: boolean// 是否禁用复制 disableCopy?: boolean// 是否禁用复制
originalImage?: string// 要进行生成的图片
createIndexPosition?: number// 创建索引位置 createIndexPosition?: number// 创建索引位置
isActive?: boolean// 是否激活 isActive?: boolean// 是否激活
} }
@@ -35,7 +34,7 @@ export class NodeManager {
/** 删除节点 */ /** 删除节点 */
deleteNode(id: string) { deleteNode(id: string) {
this.stateManager.deleteNode(id) this.stateManager.getSubordinateAllNodes(id, { isElMessageBox: true })
} }
/** 添加节点 */ /** 添加节点 */
addNode(node: any) { addNode(node: any) {
@@ -54,7 +53,6 @@ export class NodeManager {
return current.data.createIndexPosition > max.data.createIndexPosition ? current : max return current.data.createIndexPosition > max.data.createIndexPosition ? current : max
}, superiorGenerateNodes[0]) }, superiorGenerateNodes[0])
const snode = superiorID ? this.stateManager.flowManager.getNodeById(superiorID) : this.stateManager.flowManager.getLastNode(); const snode = superiorID ? this.stateManager.flowManager.getNodeById(superiorID) : this.stateManager.flowManager.getLastNode();
console.log(snode)
const id = options.id || createId() const id = options.id || createId()
const positionX = options.positionX || 0 const positionX = options.positionX || 0
const positionY = options.positionY || 0 const positionY = options.positionY || 0
@@ -83,6 +81,7 @@ export class NodeManager {
position, position,
data data
} }
if(currentNode)this.stateManager.deleteNode(currentNode.id)
this.addNode(options_) this.addNode(options_)
return options_; return options_;
} }

View File

@@ -93,7 +93,7 @@ export class StateManager {
this.nodes.value.forEach((node, index) => { this.nodes.value.forEach((node, index) => {
const superiorID = node.data.superiorID; const superiorID = node.data.superiorID;
const isSuperior = this.nodes.value.some((v) => v.id === superiorID) const isSuperior = this.nodes.value.some((v) => v.id === superiorID)
if (superiorID && isSuperior) { if (isSuperior) {
const source = node.data.superiorID const source = node.data.superiorID
const target = node.id const target = node.id
arr.push({ arr.push({
@@ -101,7 +101,7 @@ export class StateManager {
source: source, source: source,
target: target, target: target,
selectable: false, selectable: false,
visible: (node.data.isActive), visible: (node.data.type == NODE_DATATYPE.RESULT_IMAGE && node.data.isActive) || node.data.type !== NODE_DATATYPE.RESULT_IMAGE,
type: 'default' type: 'default'
}) })
} }
@@ -119,37 +119,71 @@ export class StateManager {
this.exportFlow() this.exportFlow()
} }
/** 删除节点 */ /** 删除节点 */
async deleteNode(id: string, { isElMessageBox } = { isElMessageBox: false }) { async deleteNode(id: string) {
const node = this.getNodeById(id)
if (!node) return console.warn(`没有找到指定id:${id}`)
if (node.data.disableDelete) return console.warn('该节点禁用删除')
let deletePromise: any = true
if (isElMessageBox) {
deletePromise = await new Promise<void>((resolve, reject) => {
ElMessageBox.confirm(
t('flowCanvas.deleteCardConfirm'),
'',
{
confirmButtonText: t('flowCanvas.confirm'),
cancelButtonText: t('flowCanvas.cancel'),
}
).then(() => {
resolve(true)
}).catch(() => {
resolve(false)
})
})
}
if (!deletePromise) return console.log('删除操作被取消')
this.nodes.value = this.nodes.value.filter((node: NodesItem) => node.id !== id) this.nodes.value = this.nodes.value.filter((node: NodesItem) => node.id !== id)
this.recordState()
this.exportFlow()
} }
/** 获取节点 */ /** 获取节点 */
getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) } getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) }
/** 获取下级节点 */ /** 获取下级节点 */
getSubordNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) } getSubordNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) }
getLastNode() { console.log(this.nodes.value); return this.nodes.value[this.nodes.value.length - 1] } getLastNode() { console.log(this.nodes.value); return this.nodes.value[this.nodes.value.length - 1] }
/** 获取上级生成节点的图片 */
getSuperiorNodeImage(superiorID: string) {
const superiorNode = this.getNodeById(superiorID)
if(!superiorNode){
ElMessage.error(t('flowCanvas.cannotFindSuperiorImage'))
return null
}
const superiorNodeUrl = superiorNode.data.data.imageProcessTasks.filter((item)=>{
return item.taskId == superiorNode.data.data.selectTaskId
})[0]?.url
return superiorNodeUrl
}
/** 获取下级所有子级节点 */
async getSubordinateAllNodes(id: string,{ isElMessageBox } = { isElMessageBox: false }) {
const node = this.getNodeById(id)
if (!node) return console.warn(`没有找到指定id:${id}`)
if (node.data.disableDelete) return ElMessage.error(t('flowCanvas.initialNodeProhibited'))
const result = [node]
const findChildren = (parentId: string) => {
const children = this.nodes.value.filter(item => item.data.superiorID === parentId)
children.forEach(child => {
if(child.data.type !== NODE_DATATYPE.RESULT_IMAGE){
result.push(child)
}
findChildren(child.id)
})
}
findChildren(id)
let deletePromise: any = true
if (isElMessageBox) {
deletePromise = await new Promise<void>((resolve, reject) => {
ElMessageBox.confirm(
result.length > 1 ? t('flowCanvas.deleteSubordinateCard') : t('flowCanvas.deleteCardConfirm'),
'',
{
confirmButtonText: t('flowCanvas.confirm'),
cancelButtonText: t('flowCanvas.cancel'),
}
).then(() => {resolve(true)
}).catch(() => {
resolve(false)
})
})
}
if(!deletePromise) return console.log('删除操作被取消')
this.deleteNode(id)
result.forEach(item => {
this.deleteNode(item.id)
})
this.recordState()
this.exportFlow()
}
/** 设置工具 */ /** 设置工具 */
setTool(tool: string) { this.tool.value = tool } setTool(tool: string) { this.tool.value = tool }
/** 设置光标 */ /** 设置光标 */

View File

@@ -86,8 +86,8 @@
} }
} }
.fullscreen-dialog { .fullscreen-dialog {
--size: 0.25rem; --size: 0.125rem;
--width: 4rem; --width: 2rem;
--color: #bfbfbf; --color: #bfbfbf;
background-color: #fffcf4; background-color: #fffcf4;
background-image: repeating-radial-gradient( background-image: repeating-radial-gradient(

View File

@@ -155,6 +155,9 @@ export default {
deleteSuccess: 'Successfully deleted', deleteSuccess: 'Successfully deleted',
thinking: 'Thinking...', thinking: 'Thinking...',
thinkComplete: 'Thinking complete.', thinkComplete: 'Thinking complete.',
quote: 'Quote',
delete: 'Delete',
edit: 'Edit',
}, },
// Version Tree // Version Tree
@@ -182,6 +185,9 @@ export default {
confirm: 'Confirm', confirm: 'Confirm',
cancel: 'Cancel', cancel: 'Cancel',
confirmLeave: 'Are you sure you want to leave? You may have unsaved changes.', confirmLeave: 'Are you sure you want to leave? You may have unsaved changes.',
cannotFindSuperiorImage: 'Cannot find the superior image',
deleteSubordinateCard: 'After deletion, all the function cards will also be deleted.',
initialNodeProhibited: 'Initial node is prohibited from being deleted.',
}, },
assistant: { assistant: {
inputPlaceholder: 'Ask anything', inputPlaceholder: 'Ask anything',

View File

@@ -149,7 +149,10 @@ export default {
Download: '下载', Download: '下载',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
thinking:'已思考', thinking:'已思考',
thinkComplete: '思考完成。' thinkComplete: '思考完成。',
quote: '引用',
delete: '删除',
edit: '编辑',
}, },
// Version Tree // Version Tree
@@ -183,6 +186,8 @@ export default {
confirm: '确认', confirm: '确认',
cancel: '取消', cancel: '取消',
confirmLeave: '您可能有未保存的更改,确定要离开吗?', confirmLeave: '您可能有未保存的更改,确定要离开吗?',
cannotFindSuperiorImage: '找不到上级图片',
initialNodeProhibited: 'Initial node is prohibited from being deleted.',
}, },
assistant: { assistant: {
inputPlaceholder: '请输入' inputPlaceholder: '请输入'

View File

@@ -15,6 +15,8 @@ type InitialProjectData = {
style: string style: string
useReport:boolean useReport:boolean
needSuggestion:boolean needSuggestion:boolean
quoteList: Array<string>
tempImages: any[]
} }
export const useAgentStore = defineStore('agent', () => { export const useAgentStore = defineStore('agent', () => {
const initialProjectData = ref<InitialProjectData | null>(null) const initialProjectData = ref<InitialProjectData | null>(null)

View File

@@ -64,7 +64,8 @@
region: '', region: '',
style: '' style: ''
}, },
imageUrlList: [] imageUrlList: [],
quotaUrl: []
}) })
const sketchList = ref([]) const sketchList = ref([])
@@ -110,7 +111,8 @@
text: initialData.text, text: initialData.text,
images: initialData.images, images: initialData.images,
useReport: initialData.useReport, useReport: initialData.useReport,
tempImages: initialData.tempImages tempImages: initialData.tempImages,
quoteList: initialData.quoteList
}) })
// 更新 configParams // 更新 configParams
@@ -125,6 +127,7 @@
images: Array<{ url: string; name: string }> images: Array<{ url: string; name: string }>
tempImages: any[] tempImages: any[]
useReport: boolean useReport: boolean
quoteList: Array<string>
}, },
skipUserMessage = false skipUserMessage = false
) => { ) => {
@@ -136,14 +139,14 @@
} }
params.imageUrlList = message.images || [] params.imageUrlList = message.images || []
params.quotaUrl = message.quoteList || []
// 如果不是重新生成模式,则添加用户消息到列表 // 如果不是重新生成模式,则添加用户消息到列表
if (!skipUserMessage) { if (!skipUserMessage) {
messageList.value.push({ messageList.value.push({
id: messageList.value.length + 1, id: messageList.value.length + 1,
text: message.text, text: message.text,
isUser: true, isUser: true,
imageUrls: message.tempImages imageUrls: message.tempImages.concat(message.quoteList)
}) })
} }
@@ -221,12 +224,22 @@
const decoder = new TextDecoder() const decoder = new TextDecoder()
let previousEventName = '' // 记录上一个事件名称 let previousEventName = '' // 记录上一个事件名称
let hasReportStarted = false // 标记 report 是否已经开始 let hasReportStarted = false // 标记 report 是否已经开始
let hasSketchEvent = false
let hasReportEvent = false
try { try {
let flag = true let flag = true
while (flag) { while (flag) {
const { done, value } = await reader.read() const { done, value } = await reader.read()
if (done) { if (done) {
// console.log('传输结束 end---', contentBody) if (hasSketchEvent) {
aiMessage.text += `<slot slot-name="sketch"></slot>`
}
if (hasReportEvent) {
aiMessage.text += `<slot slot-name="card" title="Report" content="Report"></slot>`
}
aiMessage.streaming = false aiMessage.streaming = false
aiMessage.loading = false aiMessage.loading = false
isGenerating.value = false isGenerating.value = false
@@ -251,7 +264,8 @@
if (!hasReportStarted && eventName === 'report') { if (!hasReportStarted && eventName === 'report') {
isGeneratingReport.value = true isGeneratingReport.value = true
contentBody += `<slot slot-name="card" title="123" content="123">123</slot>` hasReportEvent = true
// contentBody += `<slot slot-name="card" title="Report" content="Report"></slot>`
hasReportStarted = true hasReportStarted = true
} }
@@ -319,20 +333,21 @@
// console.log('jsonData', jsonData) // console.log('jsonData', jsonData)
if (jsonData.webAddress) { if (jsonData.webAddress) {
aiMessage.webAddress = JSON.parse(jsonData.webAddress) aiMessage.webAddress = JSON.parse(jsonData.webAddress)
contentBody += `<slot slot-name="url">123</slot>` contentBody += `<slot slot-name="url"></slot>`
} }
if (jsonData.title) { if (jsonData.title) {
emits('setTitle', jsonData.title) emits('setTitle', jsonData.title)
} }
if (hasSketch) { if (hasSketch) {
hasSketchEvent = true
sketchList.value.push({ sketchList.value.push({
[Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]] [Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
}) })
// 通知 Preview 有新 sketch 正在加载,传入 sketch 索引 // 通知 Preview 有新 sketch 正在加载,传入 sketch 索引
MyEvent.emit('loading-sketch', sketchList.value.length - 1) MyEvent.emit('loading-sketch', sketchList.value.length - 1)
MyEvent.emit('OpenSketch') MyEvent.emit('OpenSketch')
contentBody += `<slot slot-name="sketch"></slot>` // contentBody += `<slot slot-name="sketch"></slot>`
} }
if (eventName === 'report') { if (eventName === 'report') {
reportsContent.value += jsonData.report reportsContent.value += jsonData.report
@@ -355,6 +370,7 @@
} }
} }
if (jsonData.type === 'end') { if (jsonData.type === 'end') {
console.log('end------hasSketch', hasSketch)
aiMessage.streaming = false aiMessage.streaming = false
aiMessage.loading = false aiMessage.loading = false
isGenerating.value = false isGenerating.value = false
@@ -364,7 +380,10 @@
} catch (e) { } catch (e) {
// 检查是否为纯文本 [DONE] // 检查是否为纯文本 [DONE]
if (jsonText.trim() === '[DONE]') { if (jsonText.trim() === '[DONE]') {
console.log('done-----------hasSketch', hasSketch)
console.log('结束-----------------------') console.log('结束-----------------------')
aiMessage.text = contentBody
aiMessage.streaming = false aiMessage.streaming = false
aiMessage.loading = false aiMessage.loading = false
isGenerating.value = false isGenerating.value = false
@@ -463,11 +482,16 @@
// assistant 角色,拼接直到下一个 user // assistant 角色,拼接直到下一个 user
let combinedContent = item.content || '' let combinedContent = item.content || ''
let combinedThinkingText = item.reasoning || '' let combinedThinkingText = item.reasoning || ''
let combinedImageUrl = item.image_url || null
// 继续往后找连续的 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') {
combinedContent += dialogue[j].content || '' combinedContent += dialogue[j].content || ''
combinedThinkingText += dialogue[j].reasoning || '' combinedThinkingText += dialogue[j].reasoning || ''
// 如果有 image_url 则保留
if (dialogue[j].image_url) {
combinedImageUrl = dialogue[j].image_url
}
j++ j++
} }
@@ -476,6 +500,7 @@
content: combinedContent, content: combinedContent,
thinkingText: combinedThinkingText, thinkingText: combinedThinkingText,
text: combinedContent, text: combinedContent,
image_url: combinedImageUrl,
isUser: false, isUser: false,
id: result.length + 1, id: result.length + 1,
sessionId: sessionId sessionId: sessionId
@@ -512,7 +537,7 @@
} }
} }
// 2. 收集 report 内容并保存到 localStorage // 2. 收集 report 内容并保存到 sessionStorage
let reportStr = '' let reportStr = ''
session.dialogue?.forEach((item) => { session.dialogue?.forEach((item) => {
if (item.report) { if (item.report) {
@@ -554,36 +579,27 @@
params.configParams.style = project.style params.configParams.style = project.style
params.configParams.temperature = project.temperature params.configParams.temperature = project.temperature
} }
// 如果没有数据,直接返回
if (!data) { if (!data) {
messageList.value = [] messageList.value = []
return return
} }
const { ancestors, current } = data const { ancestors, current } = data
// 处理单个会话ancestor 或 current
const imgList = [] const imgList = []
const ancestorsList = [] const ancestorsList = []
const idCounterRef = { value: 1 } const idCounterRef = { value: 1 }
// 处理所有 ancestors
ancestors?.forEach((item) => { ancestors?.forEach((item) => {
processSession(item, imgList, ancestorsList, idCounterRef) processSession(item, imgList, ancestorsList, idCounterRef)
}) })
// 处理 current
processSession(current, imgList, ancestorsList, idCounterRef) processSession(current, imgList, ancestorsList, idCounterRef)
// 延迟设置新数据,确保 UI 有时间响应清空操作
nextTick(() => { nextTick(() => {
// 找到每个 sessionId 对应的最后一项插入sketch卡片 ancestorsList.forEach((item) => {
const sessionLastIndexMap = new Map<string, number>() if (item.image_url && item.role !== 'user') {
ancestorsList.forEach((item, index) => { item.text += `<slot slot-name="sketch"></slot>`
sessionLastIndexMap.set(item.sessionId, index) }
})
sessionLastIndexMap.forEach((lastIndex) => {
ancestorsList[lastIndex].text += '<slot slot-name="sketch"></slot>'
}) })
messageList.value = [...ancestorsList] messageList.value = [...ancestorsList]

View File

@@ -129,7 +129,7 @@
}>() }>()
// watch( // watch(
// () => props.content, // () => props,
// (newVal) => { // (newVal) => {
// console.log('newVal-----', newVal) // console.log('newVal-----', newVal)
// }, // },
@@ -139,8 +139,12 @@
const emit = defineEmits(['regenerate']) const emit = defineEmits(['regenerate'])
const imageList = computed(() => { const imageList = computed(() => {
const { imageUrls } = props.content const { imageUrls, role } = props.content
const list = [] const list = []
if (role === 'user') {
const quotaList = props.content.image_url ?? []
list.push(...quotaList)
}
if (!imageUrls || imageUrls.length === 0) return list if (!imageUrls || imageUrls.length === 0) return list
imageUrls.forEach((item) => { imageUrls.forEach((item) => {
if (typeof item === 'string') { if (typeof item === 'string') {

View File

@@ -33,7 +33,6 @@
watch( watch(
() => props.messageList, () => props.messageList,
(val) => { (val) => {
console.log('messageList',val)
scrollToBottom() scrollToBottom()
}, },
{ deep: true } { deep: true }

View File

@@ -13,12 +13,15 @@
<Menu /> <Menu />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item class="sketch-item flex align-center"> <el-dropdown-item
class="sketch-item flex align-center"
@click="handleClickQuote(item)"
>
<img <img
src="@/assets/images/restore-sketch.png" src="@/assets/images/restore-sketch.png"
class="dropdown-icon restore" class="dropdown-icon restore"
/> />
<span class="dropdown-txt">Quote</span> <span class="dropdown-txt">{{ $t('agent.quote') }}</span>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
class="sketch-item flex align-center" class="sketch-item flex align-center"
@@ -28,7 +31,7 @@
src="@/assets/images/delete.png" src="@/assets/images/delete.png"
class="dropdown-icon delete" class="dropdown-icon delete"
/> />
<span class="dropdown-txt del">Delete</span> <span class="dropdown-txt del">{{ $t('agent.delete') }}</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@@ -37,7 +40,7 @@
class="edit-btn flex align-center space-between" class="edit-btn flex align-center space-between"
@click="handleClickEdit(item)" @click="handleClickEdit(item)"
> >
<div>Edit</div> <div>{{ $t('agent.edit') }}</div>
<img src="@/assets/images/arrow-top-right.png" /> <img src="@/assets/images/arrow-top-right.png" />
</div> </div>
<!-- 已加载完成的 sketch 显示实际图片 --> <!-- 已加载完成的 sketch 显示实际图片 -->
@@ -218,6 +221,12 @@
myEvent.emit('openFlowCanvas', { url, imgId, nodeId }) myEvent.emit('openFlowCanvas', { url, imgId, nodeId })
} }
const handleClickQuote = (item) => {
// console.log(item)
const url = Object.values(item)[0]
MyEvent.emit('quote', url)
}
const handleClickDelete = (item: string | Object) => { const handleClickDelete = (item: string | Object) => {
deleteSketchFlowCanvas({ deleteSketchFlowCanvas({
id: Object.keys(item)[0], id: Object.keys(item)[0],

View File

@@ -1,35 +1,26 @@
<template> <template>
<div class="report-card" :class="{ 'is-url': isUrl, 'is-sketch': isSketch }"> <div class="report-card" :class="{ 'is-url': isUrl, 'is-sketch': isSketch }">
<div class="report-card-header"> <div class="report-card-header">
<span v-if="!isUrl && !isSketch">{{ report.title }}</span> <span v-if="!isUrl && !isSketch">{{ title || '' }}</span>
<div v-else class="web-sources flex align-center"> <div v-else class="web-sources flex align-center">
<span>{{ report.title }}</span> <span>{{ title || '' }}</span>
<img src="@/assets/images/link.png" class="link-icon" /> <img src="@/assets/images/link.png" class="link-icon" />
</div> </div>
</div> </div>
<div class="report-card-content"> <div class="report-card-content">
<span v-if="!isUrl && !isSketch">{{ report.content || 'markdown.md' }}</span> <span v-if="!isUrl && !isSketch">{{ content || 'markdown.md' }}</span>
<span v-else>{{ report.content }}</span> <span v-else>{{ content || '' }}</span>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// const props = defineProps<{
// title: string
// isUrl?: boolean
// }>()
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
title?: string title?: string
content?: string content?: string
isUrl?: boolean isUrl?: boolean
isSketch?: boolean isSketch?: boolean
report: {
title: ''
content: ''
}
}>(), }>(),
{ {
isUrl: false, isUrl: false,
@@ -50,17 +41,24 @@
background-size: 100% 100%; background-size: 100% 100%;
padding: 2.9rem; padding: 2.9rem;
overflow: hidden; overflow: hidden;
position: relative;
&.is-url { &.is-url {
background: url('@/assets/images/link-card.png') no-repeat; background: url('@/assets/images/link-card.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
} }
&.is-sketch { &.is-sketch {
background: url('@/assets/images/sketch-card.png') no-repeat; background: url('@/assets/images/sketch-card.png') no-repeat;
padding: 2rem 3rem;
max-width: 52.5rem;
height: 8rem;
box-sizing: border-box;
background-size: 100% 100%; background-size: 100% 100%;
min-height: initial;
.report-card-header {
margin-bottom: 0;
}
} }
// &:first-of-type{
// margin-top: 0;
// }
&-header { &-header {
font-family: 'Medium'; font-family: 'Medium';
font-size: 1.6rem; font-size: 1.6rem;

View File

@@ -1,10 +1,11 @@
<template> <template>
<ReportCard is-sketch :report="{title: 'Sketches Results', content: 'JPG file'}"/> <ReportCard is-sketch title="Sketches Results" content="JPG file" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import ReportCard from './ReportCard.vue' import ReportCard from './ReportCard.vue'
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
</style>
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<ReportCard is-url :report="{title: 'WebSources', content: 'Destination URL'}"/> <ReportCard is-url title="WebSources" content="Destination URL"/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -125,7 +125,6 @@ const {} = toRefs(data)
<el-drawer <el-drawer
v-model="versionTreeData.drawer" v-model="versionTreeData.drawer"
:close-on-press-escape="false" :close-on-press-escape="false"
:close-on-click-modal="false"
:size="treeState ? '73.5rem' : '73.5rem'" :size="treeState ? '73.5rem' : '73.5rem'"
body-class="versionTreeBody" body-class="versionTreeBody"
:with-header="false" :with-header="false"

View File

@@ -237,7 +237,7 @@ defineExpose({push})
margin-bottom: 0; margin-bottom: 0;
} }
> .icon{ > .icon{
margin-right: .4rem; margin-right: .8rem;
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);
} }
> span{ > span{

View File

@@ -87,7 +87,7 @@
) )
} }
const handleGetProjectInfoAndHistory = () => { const handleGetProjectInfoAndHistory = () => {
handleOpenSketch() handleOpenSketch()
getProjectInfo({ id: route.params.id }).then((res) => { getProjectInfo({ id: route.params.id }).then((res) => {
if (res) agentRef.value.setChatInfo(res) if (res) agentRef.value.setChatInfo(res)
@@ -131,7 +131,7 @@ const handleGetProjectInfoAndHistory = () => {
onMounted(() => { onMounted(() => {
MyEvent.add('openReport', handleOpenReport) MyEvent.add('openReport', handleOpenReport)
MyEvent.add('openUrls', handleOpenUrls) MyEvent.add('openUrls', handleOpenUrls)
MyEvent.add('OpenSketch', handleOpenSketch) MyEvent.add('openSketch', handleOpenSketch)
projectStore.clearProject() projectStore.clearProject()
if (proJectId.value) { if (proJectId.value) {
handleGetProjectInfoAndHistory() handleGetProjectInfoAndHistory()

View File

@@ -2,15 +2,17 @@
<div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }"> <div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }">
<div class="animate-container flex-1 flex flex-col"> <div class="animate-container flex-1 flex flex-col">
<div class="scroll-content flex-col"> <div class="scroll-content flex-col">
<div v-if="uploadedImages.length > 0" class="image-preview-list flex wrap"> <div
v-if="uploadedImages.length > 0 || quoteList.length > 0"
class="image-preview-list flex wrap"
>
<div <div
v-for="(image, index) in uploadedImages" v-for="(image, index) in [...uploadedImages, ...quoteList]"
:key="index" :key="index"
class="image-preview-item" class="image-preview-item"
> >
<img <img
:src="image.url" :src="image.url || image"
:alt="image.name"
class="preview-image" class="preview-image"
@click="previewImage(image.url)" @click="previewImage(image.url)"
/> />
@@ -35,7 +37,7 @@
</div> </div>
<div class="operate flex align-center space-between"> <div class="operate flex align-center space-between">
<div class="left flex align-center"> <div class="left flex align-center">
<div class="agent-operate flex flex-center"> <div class="agent-operate flex flex-center" v-if="isAgentMode">
<el-popover <el-popover
placement="top" placement="top"
trigger="click" trigger="click"
@@ -211,7 +213,8 @@
<div <div
v-if="!isAgentMode" v-if="!isAgentMode"
class="report-btn flex space-between align-center" class="report-btn flex space-between align-center outer"
:class="{ 'is-cn': isCn }"
@click="toogltReportTag" @click="toogltReportTag"
> >
<SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" /> <SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" />
@@ -222,7 +225,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted } from 'vue' import { computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { areaList } from '@/utils/area' import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -254,11 +257,16 @@
const emits = defineEmits(['send', 'pause']) const emits = defineEmits(['send', 'pause'])
const { t } = useI18n() const { t, locale } = useI18n()
const isCn = computed(() => {
return locale.value === 'CHINESE_SIMPLIFIED'
})
// 图片上传相关 // 图片上传相关
const fileInputRef = ref<HTMLInputElement | null>(null) const fileInputRef = ref<HTMLInputElement | null>(null)
const uploadedImages = ref<Array<{ url: string; name: string }>>([]) const uploadedImages = ref<Array<{ url: string; name: string }>>([])
const quoteList = ref<Array<string>>([])
// 触发文件上传 // 触发文件上传
const triggerFileUpload = () => { const triggerFileUpload = () => {
@@ -684,7 +692,8 @@
const payload = { const payload = {
text: inputValue.value.trim(), text: inputValue.value.trim(),
images: imageUrlList, images: imageUrlList,
tempImages: uploadedImages.value tempImages: uploadedImages.value,
quoteList: quoteList.value
} }
if (reportTags.value.length > 0) { if (reportTags.value.length > 0) {
payload.useReport = true payload.useReport = true
@@ -692,6 +701,7 @@
emits('send', payload) emits('send', payload)
// 发送后清空图片列表 // 发送后清空图片列表
uploadedImages.value = [] uploadedImages.value = []
quoteList.value = []
// 发送后清空输入框 // 发送后清空输入框
if (editorRef.value) { if (editorRef.value) {
editorRef.value.innerHTML = '' editorRef.value.innerHTML = ''
@@ -707,6 +717,7 @@
// 初始化编辑器高度 // 初始化编辑器高度
onMounted(() => { onMounted(() => {
MyEvent.add('quote', handleQuote)
nextTick(() => { nextTick(() => {
autoResizeEditor() autoResizeEditor()
}) })
@@ -803,6 +814,12 @@
previewUrl.value = url previewUrl.value = url
} }
const handleQuote = (url: string) => {
quoteList.value.push(url)
}
onUnmounted(() => {
MyEvent.remove('quote', handleQuote)
})
// 暴露方法给父组件 // 暴露方法给父组件
defineExpose({ defineExpose({
addReportTag addReportTag
@@ -821,6 +838,10 @@
background-color: #fff; background-color: #fff;
border: 1.1px solid #f6f4ef1a; border: 1.1px solid #f6f4ef1a;
cursor: pointer; cursor: pointer;
&.outer.is-cn {
justify-content: center;
column-gap: 3rem;
}
.c-svg { .c-svg {
width: 1.5rem; width: 1.5rem;
@@ -1378,7 +1399,7 @@
.agent-modal { .agent-modal {
// width: 14.6rem; // width: 14.6rem;
// height: 8.5rem; // height: 8.5rem;
row-gap: 1.2rem; row-gap: 1.2rem;
font-family: 'Medium'; font-family: 'Medium';
font-weight: 500; font-weight: 500;