fix: persist uploaded canvas reference images

This commit is contained in:
2026-05-27 15:54:22 +08:00
parent 685a6c4d64
commit ec38215dd5
4 changed files with 57 additions and 21 deletions

View File

@@ -327,6 +327,7 @@ import { Handle, Position, useVueFlow } from '@vue-flow/core'
import { NIcon, NTooltip, NSwitch, NImagePreview, NModal, NButton } from 'naive-ui'
import { TrashOutline, ExpandOutline, ImageOutline, CloseCircleOutline, CopyOutline, VideocamOutline, DownloadOutline, EyeOutline, BrushOutline, RefreshOutline, ColorWandOutline, SwapHorizontalOutline } from '@vicons/ionicons5'
import { updateNode, removeNode, duplicateNode, addNode, addEdge, nodes } from '../../stores/canvas'
import { uploadCanvasImage } from '../../hooks/useApi'
import NodeHandleMenu from './NodeHandleMenu.vue'
const props = defineProps({
@@ -665,27 +666,17 @@ const createInpaintWorkflow = () => {
window.$message?.success('已创建局部重绘工作流')
}
// Convert file to base64 | 将文件转换为 base64
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(file)
})
}
// Handle file upload | 处理文件上传
const handleFileUpload = async (event) => {
const file = event.target.files[0]
if (file) {
try {
// Convert to base64 | 转换为 base64
const base64 = await fileToBase64(file)
// Store both display URL and base64 | 同时存储显示 URL 和 base64
urlLoading.value = true
const uploaded = await uploadCanvasImage(file)
updateNode(props.id, {
url: base64, // Use base64 as display URL | 使用 base64 作为显示 URL
base64: base64, // Store base64 for API calls | 存储 base64 用于 API 调用
url: uploaded.url,
sourceJobId: uploaded.jobId,
sourceFrameIdx: uploaded.frameIdx,
fileName: file.name,
fileType: file.type,
label: '参考图',
@@ -694,6 +685,9 @@ const handleFileUpload = async (event) => {
} catch (err) {
console.error('File upload error:', err)
window.$message?.error('图片上传失败')
} finally {
urlLoading.value = false
event.target.value = ''
}
}
}
@@ -738,10 +732,12 @@ const handleReplaceFileUpload = async (event) => {
const file = event.target.files[0]
if (file) {
try {
const base64 = await fileToBase64(file)
urlLoading.value = true
const uploaded = await uploadCanvasImage(file)
updateNode(props.id, {
url: base64,
base64: base64,
url: uploaded.url,
sourceJobId: uploaded.jobId,
sourceFrameIdx: uploaded.frameIdx,
fileName: file.name,
fileType: file.type,
label: '参考图',
@@ -753,6 +749,9 @@ const handleReplaceFileUpload = async (event) => {
} catch (err) {
console.error('File upload error:', err)
window.$message?.error('图片上传失败')
} finally {
urlLoading.value = false
event.target.value = ''
}
}
}

View File

@@ -70,6 +70,18 @@ const uploadReferenceFrame = async (jobId, file) => {
return requestJson(`/jobs/${jobId}/frames/upload`, { method: 'POST', body: form })
}
export const uploadCanvasImage = async (file) => {
if (!file) throw new Error('请选择图片文件')
const job = await createCreativeImageJob(file)
const frame = job.frames?.[0]
if (!frame?.url) throw new Error('图片已上传但未返回可用地址')
return {
url: toAssetUrl(frame.url),
jobId: job.id,
frameIdx: frame.index ?? 0
}
}
const newestGeneratedImage = (job, frameIdx = 0) => {
const frame = (job.frames || []).find(item => item.index === frameIdx) || job.frames?.[0]
return [...(frame?.generated_images || [])].sort((a, b) => (b.created_at || 0) - (a.created_at || 0))[0]

View File

@@ -22,6 +22,7 @@ export const projectSyncError = ref('')
const API_BASE = import.meta.env.VITE_SKG_API_BASE || '/api'
const apiUrl = (path) => `${API_BASE}${path.startsWith('/') ? '' : '/'}${path}`
const remoteSaveTimers = new Map()
const remoteSaveSignatures = new Map()
let initPromise = null
let remoteAvailable = false
@@ -76,6 +77,12 @@ const projectToApi = (project) => ({
source: 'canvas'
})
const remoteProjectSignature = (project) => {
const payload = projectToApi(project)
delete payload.updated_at
return JSON.stringify(payload)
}
const requestJson = async (path, init = {}) => {
const response = await fetch(apiUrl(path), {
...init,
@@ -126,14 +133,17 @@ export const loadProjects = () => {
const saveRemoteProjectNow = async (project) => {
if (!project?.id) return null
const signature = remoteProjectSignature(project)
if (remoteSaveSignatures.get(project.id) === signature) return null
const response = await requestJson(`/canvas-projects/${encodeURIComponent(project.id)}`, {
method: 'PUT',
body: JSON.stringify(projectToApi(project))
})
remoteSaveSignatures.set(project.id, signature)
return response.item ? projectFromApi(response.item) : null
}
const scheduleRemoteSave = (project, delay = 800) => {
const scheduleRemoteSave = (project, delay = 2000) => {
if (!remoteAvailable || !project?.id) return
if (remoteSaveTimers.has(project.id)) {
clearTimeout(remoteSaveTimers.get(project.id))