fix(canvas): 修复生成节点状态更新及参考图请求

保留选中参考图,按模型传递分辨率,让生成节点响应成功或失败状态并显示原因。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-12 12:24:28 +08:00
co-authored by Devin
parent 6354e629ad
commit 0631849c36
2 changed files with 33 additions and 15 deletions
@@ -143,7 +143,8 @@ defineExpose({ startEdit, commitNote })
</template> </template>
<template v-else> <template v-else>
<span class="cv-node__x">✕</span> <span class="cv-node__x">✕</span>
<span class="cv-node__err" :title="node.error || ''">生成失败</span> <span>生成失败</span>
<span class="cv-node__err" :title="node.error || ''">{{ node.error || '请检查提示词后重试' }}</span>
</template> </template>
</div> </div>
</div> </div>
@@ -261,10 +262,13 @@ html.dark .cv-node__note { color: rgb(255 255 255 / 0.88); }
} }
.cv-node__err { .cv-node__err {
font-size: 11px; font-size: 11px;
max-width: 90%; line-height: 1.5;
overflow: hidden; max-width: min(90%, 320px);
text-overflow: ellipsis; max-height: 5em;
white-space: nowrap; overflow: auto;
overflow-wrap: anywhere;
text-align: center;
user-select: text;
} }
.cv-port { .cv-port {
+24 -10
View File
@@ -34,6 +34,12 @@ const imageModels = computed(() =>
) )
const model = computed(() => imageModels.value.find((m) => m.id === gen.modelId) || null) const model = computed(() => imageModels.value.find((m) => m.id === gen.modelId) || null)
const ratios = computed(() => model.value?.ratios?.length ? model.value.ratios : ['1:1']) const ratios = computed(() => model.value?.ratios?.length ? model.value.ratios : ['1:1'])
const resolutions = computed(() => model.value?.resolutions?.length
? model.value.resolutions
: Object.keys(model.value?.prices || {}))
const maxRefs = computed(() => Math.min(4, Math.max(
Number(model.value?.max_reference_images || 0), model.value?.image_to_image ? 1 : 0,
)))
const selectedImages = computed(() => const selectedImages = computed(() =>
selectionIds.value selectionIds.value
.map((id) => doc.nodes.find((n) => n.id === id)) .map((id) => doc.nodes.find((n) => n.id === id))
@@ -493,7 +499,7 @@ function onPaste(e) {
async function refToBase64(name) { async function refToBase64(name) {
const res = await fetch(generatedUrl(name)) const res = await fetch(generatedUrl(name))
if (!res.ok) throw new Error('fetch ref failed') if (!res.ok) throw new Error(`参考图加载失败 (${res.status}),请重新选择图片`)
const blob = await res.blob() const blob = await res.blob()
const dataUrl = await new Promise((resolve, reject) => { const dataUrl = await new Promise((resolve, reject) => {
const fr = new FileReader() const fr = new FileReader()
@@ -511,13 +517,18 @@ async function generate() {
genMsg.value = '请输入提示词,或选中一个有内容的笔记节点' genMsg.value = '请输入提示词,或选中一个有内容的笔记节点'
return return
} }
const images = selectedImages.value.slice(0, maxRefs.value)
if (selectedImages.value.length && !maxRefs.value) {
genMsg.value = '当前模型不支持参考图,请取消图片选择或切换模型'
return
}
genBusy.value = true genBusy.value = true
genMsg.value = '' genMsg.value = ''
const size = ratioToSize(gen.ratio || ratios.value[0]) const size = ratioToSize(gen.ratio || ratios.value[0])
const sources = [ const sources = [
...selectedNotes.value, ...selectedNotes.value,
...selectedImages.value.slice(0, 4), ...images,
] ]
// 落点:参与源节点的包围盒右侧;无源则视口中心 // 落点:参与源节点的包围盒右侧;无源则视口中心
let x let x
@@ -537,23 +548,26 @@ async function generate() {
y = c.y - size.h / 2 y = c.y - size.h / 2
} }
const node = { const node = reactive({
id: newId('g'), type: 'gen', status: 'running', id: newId('g'), type: 'gen', status: 'running',
x: Math.round(x), y: Math.round(y), w: size.w, h: size.h, z: 0, x: Math.round(x), y: Math.round(y), w: size.w, h: size.h, z: 0,
} })
doc.nodes.push(node) doc.nodes.push(node)
for (const s of sources) { for (const s of sources) {
doc.edges.push({ id: newId('e'), from: s.id, to: node.id }) doc.edges.push({ id: newId('e'), from: s.id, to: node.id })
} }
selectionIds.value = [node.id] selectionIds.value = [node.id]
stage.value?.selectIds([node.id])
pushHistory() pushHistory()
try { try {
const payload = { model: gen.modelId, prompt, ratio: gen.ratio || ratios.value[0] } const payload = {
if (selectedImages.value.length) { model: gen.modelId, prompt, ratio: gen.ratio || ratios.value[0],
payload.reference_images = await Promise.all( resolution: resolutions.value[0] || '1K',
selectedImages.value.slice(0, 4).map((n) => refToBase64(n.src).catch(() => null)), }
).then((arr) => arr.filter(Boolean)) if (images.length) {
const refs = await Promise.all(images.map((n) => refToBase64(n.src)))
payload.reference_images = refs
} }
const opts = jsonBody('POST', payload) const opts = jsonBody('POST', payload)
opts.headers['Idempotency-Key'] = node.id opts.headers['Idempotency-Key'] = node.id
@@ -834,7 +848,7 @@ const saveLabel = computed(() => ({
/> />
<div class="cv-refs"> <div class="cv-refs">
参考图:选中画布上的图片节点({{ selectedImages.length }}/4) 参考图:选中画布上的图片节点({{ selectedImages.length }}/{{ maxRefs }})
</div> </div>
<button class="cv-gen" :disabled="genBusy || !model" @click="generate"> <button class="cv-gen" :disabled="genBusy || !model" @click="generate">