fix: persist uploaded canvas reference images
This commit is contained in:
@@ -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 = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user