Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front

This commit is contained in:
X1627315083@163.com
2026-04-02 14:46:11 +08:00
21 changed files with 271 additions and 169 deletions

View File

@@ -101,12 +101,15 @@ export class AISelectboxToolManager {
} }
createIndicatorObject() { createIndicatorObject() {
this.clearIndicatorObject() this.clearIndicatorObject()
var color = 'rgba(0, 255, 0, 0.5)'
if (this.toolManager.currentTool.value === OperationType.AISELECT_REMOVE) {
color = 'rgba(255, 0, 0, 0.5)'
}
const rect = new fabric.Rect({ const rect = new fabric.Rect({
left: this.startX, left: this.startX,
top: this.startY,
width: 0, width: 0,
height: 0, height: 0,
fill: 'transparent', fill: color,
stroke: '#000', stroke: '#000',
strokeWidth: 1, strokeWidth: 1,
evented: false, evented: false,
@@ -179,10 +182,10 @@ export class AISelectboxToolManager {
mouseMoveEvent(e) { mouseMoveEvent(e) {
if (!this.targetObject) return; if (!this.targetObject) return;
if (!this.isDragging) return; if (!this.isDragging) return;
var width = e.absolutePointer.x - this.startX let width = e.absolutePointer.x - this.startX
var height = e.absolutePointer.y - this.startY let height = e.absolutePointer.y - this.startY
var left = this.startX let left = this.startX
var top = this.startY let top = this.startY
if (width < 0) { if (width < 0) {
left += width left += width
width = -width width = -width

View File

@@ -17,7 +17,7 @@ fabric.Object.prototype.toObject = function () {
const arr = [...fabric.Object.prototype.customProperties] const arr = [...fabric.Object.prototype.customProperties]
args.forEach(v => (Array.isArray(v) ? arr.push(...v) : arr.push(v))) args.forEach(v => (Array.isArray(v) ? arr.push(...v) : arr.push(v)))
if (this.fill?.source === null) { if (this.fill?.source === null) {
let image = new Image() const image = new Image()
image.crossOrigin = 'anonymous' image.crossOrigin = 'anonymous'
image.src = this.info?.fill?.source image.src = this.info?.fill?.source
this.fill.source = image this.fill.source = image
@@ -65,17 +65,17 @@ export class CanvasManager {
} }
} }
setCanvasViewSize(options) { setCanvasViewSize(options) {
var canvasViewWidth = options.canvasViewWidth || 1920 const canvasViewWidth = options.canvasViewWidth || 1920
var canvasViewHeight = options.canvasViewHeight || 1080 const canvasViewHeight = options.canvasViewHeight || 1080
this.canvas.setWidth(canvasViewWidth) this.canvas.setWidth(canvasViewWidth)
this.canvas.setHeight(canvasViewHeight) this.canvas.setHeight(canvasViewHeight)
} }
/** 初始化画布 */ /** 初始化画布 */
async initCanvas(options: CanvasInitOptions) { async initCanvas(options: CanvasInitOptions) {
this.layerManager = this.stateManager.layerManager this.layerManager = this.stateManager.layerManager
var canvasWidth = options.canvasWidth || 750 let canvasWidth = options.canvasWidth || 750
var canvasHeight = options.canvasHeight || 600 let canvasHeight = options.canvasHeight || 600
var image = null; let image = null;
if (options.url) { if (options.url) {
await new Promise((resolve) => { await new Promise((resolve) => {
fabric.Image.fromURL(options.url, async (img) => { fabric.Image.fromURL(options.url, async (img) => {
@@ -101,8 +101,6 @@ export class CanvasManager {
}) })
} }
this.canvas = createCanvas(options.canvasRef.value, { this.canvas = createCanvas(options.canvasRef.value, {
preserveObjectStacking: true,
enableRetinaScaling: true,
backgroundColor: '#fff', backgroundColor: '#fff',
}) })
if (image) { if (image) {
@@ -188,8 +186,8 @@ export class CanvasManager {
async updateSubLayerClipPath() { async updateSubLayerClipPath() {
const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id); const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id);
for (let i = 0; i < objects.length; i++) { for (let i = 0; i < objects.length; i++) {
let object = objects[i] const object = objects[i]
let path = this.getObjectById(object.info.parentId)?.clipPath const path = this.getObjectById(object.info.parentId)?.clipPath
object.set({ clipPath: path || null }) object.set({ clipPath: path || null })
} }
this.renderAll() this.renderAll()
@@ -381,7 +379,7 @@ export class CanvasManager {
} }
/** 处理JSON为正常画布 */ /** 处理JSON为正常画布 */
processCanvasDisUrlJSON(obj: { canvas: string, images: Object }) { processCanvasDisUrlJSON(obj: { canvas: string, images: Object }) {
var json = obj.canvas; let json = obj.canvas;
const images = obj.images || {} const images = obj.images || {}
for (const key in images) { for (const key in images) {
json = json.replace(new RegExp(key), images[key]) json = json.replace(new RegExp(key), images[key])

View File

@@ -64,10 +64,10 @@ export class LayerManager {
getLayerById(id) { getLayerById(id) {
function call(arr) { function call(arr) {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
let v = arr[i] const v = arr[i]
if (v.info.id === id) return v if (v.info.id === id) return v
if (v.children) { if (v.children) {
let layer = call(v.children) const layer = call(v.children)
if (layer) return layer if (layer) return layer
} }
} }

View File

@@ -220,8 +220,8 @@ export class ObjectManager {
const isWidth = object.hasOwnProperty('width') const isWidth = object.hasOwnProperty('width')
const isHeight = object.hasOwnProperty('height') const isHeight = object.hasOwnProperty('height')
if (isWidth || isHeight) { if (isWidth || isHeight) {
let width = options.width const width = isWidth ? options.width : object.width
let height = options.height const height = isHeight ? options.height : object.height
if (type === "polygon") { if (type === "polygon") {
if (object.points.length === 10) {// 五角星 if (object.points.length === 10) {// 五角星
options.points = getStarArr(width, height) options.points = getStarArr(width, height)

View File

@@ -58,10 +58,10 @@ export class ShapeToolManager {
} }
mouseMoveEvent(e) { mouseMoveEvent(e) {
if (!this.isDragging) return; if (!this.isDragging) return;
var width = e.absolutePointer.x - this.startX let width = e.absolutePointer.x - this.startX
var height = e.absolutePointer.y - this.startY let height = e.absolutePointer.y - this.startY
var left = this.startX let left = this.startX
var top = this.startY let top = this.startY
if (width < 0) { if (width < 0) {
left += width left += width
width = -width width = -width

View File

@@ -89,7 +89,7 @@ export class StateManager {
/** 撤回状态 */ /** 撤回状态 */
undoState() { undoState() {
if (this.running.value) return if (this.running.value) return
var index = this.historyIndex.value - 1 const index = this.historyIndex.value - 1
const state = this.historyList.value[index] const state = this.historyList.value[index]
if (!state) return if (!state) return
this.running.value = true this.running.value = true
@@ -103,7 +103,7 @@ export class StateManager {
/** 重做状态 */ /** 重做状态 */
redoState() { redoState() {
if (this.running.value) return if (this.running.value) return
var index = this.historyIndex.value + 1 const index = this.historyIndex.value + 1
const state = this.historyList.value[index] const state = this.historyList.value[index]
if (!state) return if (!state) return
this.running.value = true this.running.value = true

View File

@@ -6,8 +6,9 @@ import { fabric } from "fabric-with-all";
export const createCanvas = (elementId, options = {}) => { export const createCanvas = (elementId, options = {}) => {
// Create the canvas instance // Create the canvas instance
const canvas = new fabric.Canvas(elementId, { const canvas = new fabric.Canvas(elementId, {
enableRetinaScaling: true, controlsAboveOverlay: true,// 控制面板在图层之上
renderOnAddRemove: false, enableRetinaScaling: true,// 启用Retina缩放
renderOnAddRemove: false,// 不在添加或移除对象时重新渲染
preserveObjectStacking: true, // 保持对象堆叠顺序 preserveObjectStacking: true, // 保持对象堆叠顺序
// skipOffscreen: true, // 跳过离屏渲染 // skipOffscreen: true, // 跳过离屏渲染
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿 imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿
@@ -26,7 +27,7 @@ export const createCanvas = (elementId, options = {}) => {
*/ */
export const createStaticCanvas = (elementId, options = {}) => { export const createStaticCanvas = (elementId, options = {}) => {
const canvas = new fabric.StaticCanvas(elementId, { const canvas = new fabric.StaticCanvas(elementId, {
enableRetinaScaling: true, enableRetinaScaling: true,// 启用Retina缩放
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿 imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿
imageSmoothingQuality: "high", // 设置高质量图像平滑 imageSmoothingQuality: "high", // 设置高质量图像平滑
skipOffscreen: false, // 不跳过离屏渲染 skipOffscreen: false, // 不跳过离屏渲染

View File

@@ -16,8 +16,8 @@ export const initThree = (threeDom)=>{
/** /**
* 创建渲染器对象 * 创建渲染器对象
*/ */
let width = threeDom.offsetWidth; //窗口宽度 const width = threeDom.offsetWidth; //窗口宽度
let height = threeDom.offsetHeight; //窗口高度 const height = threeDom.offsetHeight; //窗口高度
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
antialias: true, antialias: true,
logarithmicDepthBuffer: true,//深度缓存 防止模型闪烁重影 logarithmicDepthBuffer: true,//深度缓存 防止模型闪烁重影
@@ -35,9 +35,9 @@ export const initThree = (threeDom)=>{
// 设置渲染器大小 // 设置渲染器大小
//环境光 //环境光
let ambient = new THREE.AmbientLight(0xffffff,.8); const ambient = new THREE.AmbientLight(0xffffff,.8);
scene.add(ambient); scene.add(ambient);
let controls = new OrbitControls(camera,renderer.domElement)//监听鼠标、键盘事件; const controls = new OrbitControls(camera,renderer.domElement)//监听鼠标、键盘事件;
// controls.minDistance = 500; // 设置相机与焦点的最小距离 // controls.minDistance = 500; // 设置相机与焦点的最小距离
// controls.maxDistance = 4000; // 设置相机与焦点的最大距离 // controls.maxDistance = 4000; // 设置相机与焦点的最大距离
controls.mouseButtons = { controls.mouseButtons = {
@@ -166,14 +166,14 @@ export const addModel = async (
load: any load: any
) => { ) => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
var fbxLoader = new GLTFLoader(); const fbxLoader = new GLTFLoader();
let drac = new DRACOLoader() const drac = new DRACOLoader()
drac.setDecoderPath('/draco/') drac.setDecoderPath('/draco/')
fbxLoader.setDRACOLoader(drac) fbxLoader.setDRACOLoader(drac)
fbxLoader.load(url, fbxLoader.load(url,
(obj: any) => { (obj: any) => {
let scene = obj.scene; const scene = obj.scene;
scene.traverse((child: any) => { scene.traverse((child: any) => {
if (child.isMesh) { if (child.isMesh) {
// 如果是基础材质,转换为标准材质 // 如果是基础材质,转换为标准材质

View File

@@ -27,13 +27,13 @@ export class GenerateManager {
async getTasksIdImg() { async getTasksIdImg() {
clearInterval(this.getTaskIdsImgTime) clearInterval(this.getTaskIdsImgTime)
this.getTaskIdsImgTime = setInterval(()=>{ this.getTaskIdsImgTime = setInterval(()=>{
let taskIds = this.taskIds.map((item)=>item.taskId) const taskIds = this.taskIds.map((item)=>item.taskId)
getTaskidResult({taskIds}).then((rv:any)=>{ getTaskidResult({taskIds}).then((rv:any)=>{
//找出成功和失败的任务 //找出成功和失败的任务
let returnedTasks = rv.filter((item)=>item.status == 'RETURNED' || item.status == 'FAILED') const returnedTasks = rv.filter((item)=>item.status == 'RETURNED' || item.status == 'FAILED')
if(returnedTasks.length == 0)return if(returnedTasks.length == 0)return
//剔除调成功的 //剔除调成功的
let taskIds_ = JSON.parse(JSON.stringify(this.taskIds)) const taskIds_ = JSON.parse(JSON.stringify(this.taskIds))
this.taskIds = taskIds_.filter(itemA => this.taskIds = taskIds_.filter(itemA =>
!returnedTasks.some(itemB => itemB.taskId === itemA.taskId) !returnedTasks.some(itemB => itemB.taskId === itemA.taskId)
) )

View File

@@ -142,7 +142,7 @@ export class NodeManager {
copyNodeById(id: string) { copyNodeById(id: string) {
const node = this.stateManager.getNodeById(id) const node = this.stateManager.getNodeById(id)
let copyNode = JSON.parse(JSON.stringify(node)) const copyNode = JSON.parse(JSON.stringify(node))
const flowNode = this.stateManager.flowManager.getNodeById(id) const flowNode = this.stateManager.flowManager.getNodeById(id)
if (!node) return console.warn(`${id}找不到对应节点`) if (!node) return console.warn(`${id}找不到对应节点`)
if (node.data?.disableCopy) return console.warn(`${id}节点已禁用复制`) if (node.data?.disableCopy) return console.warn(`${id}节点已禁用复制`)

View File

@@ -276,7 +276,7 @@ export class StateManager {
} }
/** 撤回状态 */ /** 撤回状态 */
undoState() { undoState() {
var index = this.historyIndex.value - 1 const index = this.historyIndex.value - 1
const state = this.historyList.value[index] const state = this.historyList.value[index]
if (!state) return if (!state) return
this.historyIndex.value = index this.historyIndex.value = index
@@ -285,7 +285,7 @@ export class StateManager {
} }
/** 重做状态 */ /** 重做状态 */
redoState() { redoState() {
var index = this.historyIndex.value + 1 const index = this.historyIndex.value + 1
const state = this.historyList.value[index] const state = this.historyList.value[index]
if (!state) return if (!state) return
this.historyIndex.value = index this.historyIndex.value = index

View File

@@ -1,20 +0,0 @@
// 定义要忽略的警告关键词列表
const ignoredWarnings = [
// '`markRaw` or using `shallowRef` instead of `ref`',
]
/** 忽略组件响应式警告 */
export default function (app) {
// 只忽略组件响应式警告
app.config.warnHandler = (msg, instance, trace) => {
// 检查是否包含要忽略的关键词
const shouldIgnore = ignoredWarnings.some(warning =>
msg.includes(warning)
)
// 如果不应该忽略,才输出警告
if (!shouldIgnore) {
console.warn(msg, instance, trace)
}
}
}

View File

@@ -16,10 +16,8 @@ import "./router/router-config" // 路由守卫,做动态路由的地方
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import ignoredWarning from './ignoredWarning'
const app = createApp(App) const app = createApp(App)
ignoredWarning(app)
app.use(router) app.use(router)
.use(directives) .use(directives)
.use(ElementPlus) .use(ElementPlus)

View File

@@ -5,7 +5,6 @@ import { ref, computed } from 'vue'
import { removeLocal, setLocal } from '@/utils/local' import { removeLocal, setLocal } from '@/utils/local'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
// Agent 项目初始数据 store // Agent 项目初始数据 store
type InitialProjectData = { type InitialProjectData = {
text: string text: string
@@ -13,13 +12,13 @@ type InitialProjectData = {
type: string type: string
area: string area: string
style: string style: string
useReport:boolean useReport: boolean
needSuggestion:boolean needSuggestion: boolean
quoteList: Array<string> quoteList: Array<string>
tempImages: any[] tempImages: any[]
} }
export const useAgentStore = defineStore('agent', () => { export const useAgentStore = defineStore('agent', () => {
const initialProjectData = ref<InitialProjectData | null>(null) const initialProjectData = ref<InitialProjectData | null>(null)
// 保存项目初始数据 // 保存项目初始数据
const setInitialProjectData = (data: InitialProjectData) => { const setInitialProjectData = (data: InitialProjectData) => {
@@ -34,10 +33,94 @@ export const useAgentStore = defineStore('agent', () => {
initialProjectData.value = null initialProjectData.value = null
} }
// 会话缓存管理,最多保存 10 条会话
const conversationCache = new Map<string, any>()
const MAX_CACHE_ENTRIES = 10
const SESSION_KEY_PREFIX = 'agent_session_'
const pruneCache = () => {
const keys = Array.from(conversationCache.keys())
const total = conversationCache.size
if (total <= MAX_CACHE_ENTRIES) return
for (let i = 0; i < total - MAX_CACHE_ENTRIES; i++) {
conversationCache.delete(keys[i])
sessionStorage.removeItem(SESSION_KEY_PREFIX + keys[i])
}
}
const saveCacheConversation = (projectId: string, data: any) => {
if (!projectId) return
const finalData = {
...data,
updatedAt: Date.now()
}
if (conversationCache.has(projectId)) {
conversationCache.delete(projectId)
}
conversationCache.set(projectId, finalData)
try {
sessionStorage.setItem(SESSION_KEY_PREFIX + projectId, JSON.stringify(finalData))
} catch (e) {
console.warn('saveCacheConversation sessionStorage failed', e)
}
pruneCache()
}
const getCacheConversation = (projectId: string) => {
if (!projectId) return null
let data = conversationCache.get(projectId)
if (data) return data
try {
const raw = sessionStorage.getItem(SESSION_KEY_PREFIX + projectId)
if (!raw) return null
data = JSON.parse(raw)
conversationCache.set(projectId, data)
return data
} catch (e) {
console.warn('getCacheConversation failed', e)
return null
}
}
const clearAllCacheConversations = () => {
conversationCache.value.clear()
for (const key in window.sessionStorage) {
if (key.startsWith(SESSION_KEY_PREFIX)) {
sessionStorage.removeItem(key)
}
}
}
const removeCacheConversation = (projectId: string) => {
if (!projectId) return
conversationCache.value.delete(projectId)
sessionStorage.removeItem(SESSION_KEY_PREFIX + projectId)
}
const getCacheStats = () => {
return {
total: conversationCache.value.size,
keys: Array.from(conversationCache.value.keys())
}
}
return { return {
initialProjectData, initialProjectData,
setInitialProjectData, setInitialProjectData,
getInitialProjectData, getInitialProjectData,
clearInitialProjectData clearInitialProjectData,
conversationCache,
saveCacheConversation,
getCacheConversation,
removeCacheConversation,
clearAllCacheConversations,
getCacheStats
} }
}) })

View File

@@ -19,7 +19,7 @@ export const useVersionTreeStore = defineStore('versionTree', () => {
}) })
const setNodeDetail = (v: any) => { const setNodeDetail = (v: any) => {
for(let key in v){ for(const key in v){
state.value.nodeDetail[key] = v[key] state.value.nodeDetail[key] = v[key]
} }
} }

View File

@@ -114,8 +114,8 @@ service.interceptors.response.use(
return Promise.reject(false) return Promise.reject(false)
} }
error.config && removePending(error.config) error.config && removePending(error.config)
console.log('err' + error) // for debug // console.log('err', error) // for debug
ElMessage.error(error.message) ElMessage.error(error.response?.data?.message || error.message)
} }
return Promise.reject(error) return Promise.reject(error)
} }

View File

@@ -80,6 +80,71 @@
{ deep: true } { deep: true }
) )
const saveSession = (projectId: string) => {
if (!projectId) return
agentStore.saveCacheConversation(projectId, {
messageList: messageList.value,
sketchList: sketchList.value,
params: {
...params,
projectID: projectId
},
isGenerating: isGenerating.value,
isPaused: isPaused.value
})
}
const restoreSession = (projectId: string) => {
if (!projectId) return false
const payload = agentStore.getCacheConversation(projectId)
if (!payload) return false
messageList.value = payload.messageList || []
sketchList.value = payload.sketchList || []
if (payload.params) {
params.projectID = payload.params.projectID || projectId
params.message = payload.params.message || ''
params.versionID = payload.params.versionID || ''
params.configParams = payload.params.configParams || params.configParams
params.needSuggestion = payload.params.needSuggestion || false
params.useReport = payload.params.useReport || false
params.imageUrlList = payload.params.imageUrlList || []
params.quotaUrl = payload.params.quotaUrl || []
}
isGenerating.value = payload.isGenerating || false
isPaused.value = payload.isPaused || false
return true
}
const clearSession = (projectId: string) => {
if (!projectId) return
agentStore.removeCacheConversation(projectId)
}
const handleReset = (force = false) => {
if (!force && params.projectID) {
saveSession(params.projectID)
}
if (force && params.projectID) {
clearSession(params.projectID)
}
messageList.value = []
sketchList.value = []
params.versionID = ''
params.imageUrlList = []
params.quotaUrl = []
params.needSuggestion = false
params.useReport = false
params.configParams = {
type: '',
region: '',
style: ''
}
isGenerating.value = false
isPaused.value = false
}
// 每次请求时创建新的 AbortController // 每次请求时创建新的 AbortController
let abort: AbortController let abort: AbortController
@@ -93,10 +158,15 @@
} }
onUnmounted(() => { onUnmounted(() => {
if (params.projectID) {
saveSession(params.projectID)
}
abort?.abort() abort?.abort()
MyEvent.remove('resetAgent', handleReset)
}) })
onMounted(() => { onMounted(() => {
MyEvent.add('resetAgent', handleReset)
// 检查 store 中是否有初始项目数据 // 检查 store 中是否有初始项目数据
// projectStore.setId('1') // 临时设置项目ID为1实际应用中应根据上下文动态设置 // projectStore.setId('1') // 临时设置项目ID为1实际应用中应根据上下文动态设置
const initialData = agentStore.getInitialProjectData const initialData = agentStore.getInitialProjectData
@@ -353,10 +423,7 @@
}) })
} }
}) })
// 通知 Preview 有新 sketch 正在加载,传入 sketch 索引
MyEvent.emit('loading-sketch', sketchList.value.length - 1)
MyEvent.emit('OpenSketch') MyEvent.emit('OpenSketch')
// contentBody += `<slot slot-name="sketch"></slot>`
} }
if (eventName === 'reportName' || eventName === 'reportTitle') { if (eventName === 'reportName' || eventName === 'reportTitle') {
aiMessage.reportName = jsonData.reportName || jsonData.reportTitle aiMessage.reportName = jsonData.reportName || jsonData.reportTitle
@@ -438,6 +505,9 @@
isPaused.value = true isPaused.value = true
isGenerating.value = false isGenerating.value = false
abort?.abort() abort?.abort()
if (params.projectID) {
saveSession(params.projectID)
}
MyEvent.emit('stopChat') MyEvent.emit('stopChat')
} }
@@ -470,13 +540,7 @@
} }
// 处理对话列表,将连续的 assistant 消息合并为一条 // 处理对话列表,将连续的 assistant 消息合并为一条
const processDialogue = ( const processDialogue = (dialogue, startIndex, existingImgList, sessionId) => {
dialogue,
startIndex,
existingImgList,
sessionId,
firstReportIndex = -1
) => {
if (!dialogue || dialogue.length === 0) return [] if (!dialogue || dialogue.length === 0) return []
const result = [] const result = []
@@ -485,7 +549,6 @@
while (i < dialogue.length) { while (i < dialogue.length) {
const item = dialogue[i] const item = dialogue[i]
if (item.role === 'user') { if (item.role === 'user') {
// user 角色直接添加
result.push({ result.push({
...item, ...item,
text: item.content, text: item.content,
@@ -495,47 +558,47 @@
}) })
i++ i++
} else if (item.role === 'assistant') { } else if (item.role === 'assistant') {
// assistant 角色,拼接直到下一个 user
let combinedContent = item.content || '' let combinedContent = item.content || ''
let combinedThinkingText = item.reasoning || '' let combinedThinkingText = item.reasoning || ''
let combinedImageUrl = item.image_url || null let combinedImageUrl = item.image_url || null
let reportName = item.reportName || null let reportName = item.reportName || null
let webAddress = item.webAddress || null let webAddress = item.webAddress || null
// 继续往后找连续的 assistant 消息 let combinedReport = item.report || ''
let hasReportSlot = !!item.report
let hasUrlSlot = !!item.webAddress
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 || '' const next = dialogue[j]
combinedThinkingText += dialogue[j].reasoning || '' combinedContent += next.content || ''
// 如果有 image_url 则保留 combinedThinkingText += next.reasoning || ''
if (dialogue[j].image_url) { combinedReport += next.report || ''
combinedImageUrl = dialogue[j].image_url if (next.image_url) combinedImageUrl = next.image_url
} if (next.reportName) reportName = next.reportName
if (dialogue[j].reportName) { if (next.report) hasReportSlot = true
reportName = dialogue[j].reportName if (next.webAddress) {
} webAddress = next.webAddress
if (dialogue[j].webAddress) { hasUrlSlot = true
combinedContent += `<slot slot-name="url"></slot>`
webAddress = dialogue[j].webAddress
// console.log('webAddress22222222222222', dialogue[j].webAddress)
// debugger
} }
j++ j++
} }
// 如果 firstReportIndex 在当前合并范围内,则把 slot 追加到末尾 if (hasUrlSlot) {
if (firstReportIndex >= i && firstReportIndex < j) { combinedContent += `<slot slot-name="url"></slot>`
}
if (hasReportSlot) {
combinedContent += `<slot slot-name="card" title="Report" content="Report"></slot>` combinedContent += `<slot slot-name="card" title="Report" content="Report"></slot>`
} }
result.push({ result.push({
...item, ...item,
reportName, reportName,
report: combinedReport,
content: combinedContent, content: combinedContent,
thinkingText: combinedThinkingText, thinkingText: combinedThinkingText,
text: combinedContent, text: combinedContent,
image_url: combinedImageUrl, image_url: combinedImageUrl,
webAddress: !!webAddress ? JSON.parse(webAddress) : null, webAddress: webAddress ? JSON.parse(webAddress) : null,
isUser: false, isUser: false,
id: result.length + 1, id: result.length + 1,
sessionId: sessionId sessionId: sessionId
@@ -543,7 +606,6 @@
i = j i = j
} else { } else {
// 其他角色直接添加
result.push({ result.push({
...item, ...item,
text: item.content, text: item.content,
@@ -560,35 +622,11 @@
const processSession = (session, imgList, ancestorsList, idCounterRef) => { const processSession = (session, imgList, ancestorsList, idCounterRef) => {
if (!session) return if (!session) return
// 1. 找到第一个 report 项的索引,供 processDialogue 使用
let firstReportIndex = -1
if (session.dialogue) {
for (let i = 0; i < session.dialogue.length; i++) {
if (session.dialogue[i].report) {
firstReportIndex = i
break
}
}
}
// 2. 收集 report 内容并保存到 sessionStorage
let reportStr = ''
session.dialogue?.forEach((item) => {
if (item.report) {
reportStr += item.report
}
})
if (reportStr && session.id) {
sessionStorage.setItem(`reportsContent_${session.id}`, reportStr)
}
// 3. 收集 sketchIDAndUrl 到 imgList
if (session.sketchIDAndUrl) { if (session.sketchIDAndUrl) {
imgList.push(session.sketchIDAndUrl) imgList.push(session.sketchIDAndUrl)
} }
// 4. 处理 dialogue const list = processDialogue(session.dialogue, 0, imgList, session.id)
const list = processDialogue(session.dialogue, 0, imgList, session.id, firstReportIndex)
list.forEach((el) => { list.forEach((el) => {
el.id = idCounterRef.value++ el.id = idCounterRef.value++
}) })
@@ -636,6 +674,7 @@
item.text += `<slot slot-name="sketch"></slot>` item.text += `<slot slot-name="sketch"></slot>`
} }
}) })
console.log('ancestorslist', ancestorsList)
messageList.value = [...ancestorsList] messageList.value = [...ancestorsList]
params.versionID = current?.id params.versionID = current?.id
sketchList.value = imgList sketchList.value = imgList
@@ -643,7 +682,10 @@
} }
defineExpose({ defineExpose({
setChatInfo setChatInfo,
saveSession,
restoreSession,
clearSession
}) })
</script> </script>

View File

@@ -161,12 +161,13 @@
const quotaList = props.content.image_url ?? [] const quotaList = props.content.image_url ?? []
list.push(...quotaList) 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') {
list.push(item) list.push(item)
} else if (typeof item === 'object' && item.url) { } else if (typeof item === 'object' && item?.url) {
list.push(item.url) list.push(item?.url)
} }
}) })
return list return list

View File

@@ -2,7 +2,7 @@
<div class="agent-list flex flex-col flex-1" ref="listContainer"> <div class="agent-list flex flex-col flex-1" ref="listContainer">
<Item <Item
v-for="(message, index) in messageList" v-for="(message, index) in messageList"
:key="message.id" :key="message.id + index"
:content="message" :content="message"
:is-last="index === messageList.length - 1" :is-last="index === messageList.length - 1"
@regenerate="$emit('regenerate', message)" @regenerate="$emit('regenerate', message)"

View File

@@ -44,10 +44,7 @@
<img src="@/assets/images/arrow-top-right.png" /> <img src="@/assets/images/arrow-top-right.png" />
</div> </div>
<!-- 已加载完成的 sketch 显示实际图片 --> <!-- 已加载完成的 sketch 显示实际图片 -->
<img <img v-img-loading="getImageSrc(item, index)" @load="handleImageLoad(index)" />
v-img-loading="getImageSrc(item, index)"
@load="handleImageLoad(index)"
/>
<!-- 正在加载的 sketch 显示 loading gif overlay --> <!-- 正在加载的 sketch 显示 loading gif overlay -->
<!-- <div v-if="pendingSketchIndexes.includes(index)" class="loading-wrapper"> <!-- <div v-if="pendingSketchIndexes.includes(index)" class="loading-wrapper">
<img src="@/assets/images/sketch-loading.gif" alt="loading" /> <img src="@/assets/images/sketch-loading.gif" alt="loading" />
@@ -118,8 +115,6 @@
// 存储每个图片的加载状态 // 存储每个图片的加载状态
const loadedStatus = ref<boolean[]>([]) const loadedStatus = ref<boolean[]>([])
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
type: 'sketch' | 'report' | 'url' type: 'sketch' | 'report' | 'url'
@@ -187,8 +182,9 @@
sessionId.value = id sessionId.value = id
} }
const setReportTitle = (title: string) => { const setReport = (title: string, content: string) => {
reportTitle.value = title reportTitle.value = title
markdownContent.value = content
} }
const setUrls = async (list: string[]) => { const setUrls = async (list: string[]) => {
@@ -196,14 +192,14 @@
const res = await fetchUrlTitle(list) const res = await fetchUrlTitle(list)
urlList.value = res urlList.value = res
} }
watch( // watch(
() => sessionId.value, // () => sessionId.value,
(newVal) => { // (newVal) => {
if (newVal) { // if (newVal) {
markdownContent.value = sessionStorage.getItem(`reportsContent_${newVal}`) // markdownContent.value = sessionStorage.getItem(`reportsContent_${newVal}`)
} // }
} // }
) // )
// 图片加载完成时触发 // 图片加载完成时触发
const handleImageLoad = (index: number) => { const handleImageLoad = (index: number) => {
@@ -264,15 +260,6 @@
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }
const showLoading = ref(false)
const handleLoadingSketch = (sketchIndex?: number) => {
showLoading.value = true
// 记录正在加载的 sketch 索引
// if (sketchIndex !== undefined) {
// pendingSketchIndexes.value.push(sketchIndex)
// }
}
const handleClickUrl = (item: { url: string; title: string }) => { const handleClickUrl = (item: { url: string; title: string }) => {
window.open(item.url, '_blank') window.open(item.url, '_blank')
} }
@@ -316,17 +303,11 @@
} }
} }
} }
onMounted(() => {
MyEvent.add('loading-sketch', handleLoadingSketch)
})
onUnmounted(() => {
MyEvent.remove('loading-sketch', handleLoadingSketch)
})
defineExpose({ defineExpose({
setSessionId, setSessionId,
setUrls, setUrls,
setReportTitle setReport
}) })
</script> </script>

View File

@@ -116,7 +116,7 @@
const handleOpenReport = (data) => { const handleOpenReport = (data) => {
previewRef.value.setSessionId(data.sessionId) previewRef.value.setSessionId(data.sessionId)
previewRef.value.setReportTitle(data.reportName) previewRef.value.setReport(data.reportName, data.report)
previewType.value = 'report' previewType.value = 'report'
} }
@@ -131,10 +131,25 @@
watch( watch(
() => proJectId.value, () => proJectId.value,
(newVal, oldVal) => { async (newVal, oldVal) => {
if (oldVal && agentRef.value && typeof agentRef.value.saveSession === 'function') {
agentRef.value.saveSession(oldVal as string)
}
projectStore.clearProject() projectStore.clearProject()
if (newVal) { if (newVal) {
handleGetProjectInfoAndHistory() let restored = false
if (agentRef.value && typeof agentRef.value.restoreSession === 'function') {
restored = await agentRef.value.restoreSession(newVal as string)
}
if (!restored) {
handleGetProjectInfoAndHistory()
}
MyEvent.emit('projectChange')
} else {
MyEvent.emit('projectChange') MyEvent.emit('projectChange')
} }
} }