From cc47603ca245adae79f58c71df69f04292a432a6 Mon Sep 17 00:00:00 2001 From: XianYuDaXian Date: Sat, 28 Feb 2026 16:59:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20workbench=20?= =?UTF-8?q?=E5=8F=82=E8=80=83=E5=9B=BE=20URL=20=E4=B8=8A=E4=BC=A0=E4=B8=8E?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/grok/utils/upload.py | 13 ++- app/static/public/js/imagine_workbench.js | 122 ++++++++++++++-------- 2 files changed, 90 insertions(+), 45 deletions(-) diff --git a/app/services/grok/utils/upload.py b/app/services/grok/utils/upload.py index ef7788c..de631b0 100644 --- a/app/services/grok/utils/upload.py +++ b/app/services/grok/utils/upload.py @@ -249,7 +249,12 @@ class UploadService: lock_timeout = max(1, int(get_config("asset.upload_timeout"))) async with _file_lock(lock_name, timeout=lock_timeout): session = await self.create() - response = await session.get(url, timeout=timeout, proxies=proxies) + response = await session.get( + url, + timeout=timeout, + proxies=proxies, + stream=True, + ) if response.status_code >= 400: raise UpstreamException( message=f"Failed to fetch: {response.status_code}", @@ -263,7 +268,11 @@ class UploadService: if not content_type: content_type = self._infer_mime(filename) if hasattr(response, "aiter_content"): - b64 = await self._encode_b64_stream(response.aiter_content()) + try: + b64 = await self._encode_b64_stream(response.aiter_content()) + except Exception: + # 某些响应对象虽存在 aiter_content,但底层未进入 stream 模式;回退读取 content。 + b64 = base64.b64encode(response.content).decode() else: b64 = base64.b64encode(response.content).decode() diff --git a/app/static/public/js/imagine_workbench.js b/app/static/public/js/imagine_workbench.js index 2c920a1..cdac10d 100644 --- a/app/static/public/js/imagine_workbench.js +++ b/app/static/public/js/imagine_workbench.js @@ -199,6 +199,7 @@ function applyParentPostFromText(text, options = {}) { const silent = Boolean(options.silent); + const addToReferences = Boolean(options.addToReferences); const hit = resolveParentMemoryByText(text); if (!hit || !hit.parentPostId) { if (!silent) { @@ -225,8 +226,43 @@ imageUrl: previewUrl, origin: 'workbench_paste_apply', }); + if (addToReferences) { + const refUrl = String(sourceImageUrl || previewUrl || '').trim(); + if (!refUrl) { + if (!silent) { + toast('该 parentPostId 未解析到可用参考图', 'warning'); + } + } else if (state.referenceImages.length >= REFERENCE_LIMIT) { + if (!silent) { + toast(`最多支持 ${REFERENCE_LIMIT} 张参考图`, 'warning'); + } + } else { + const exists = state.referenceImages.some((item) => ( + (item.parentPostId && item.parentPostId === parentPostId) + || String(item.data || '').trim() === refUrl + )); + if (!exists) { + state.referenceImages.push({ + id: buildRefId(), + name: `parent:${shortId(parentPostId)}`, + mime: '', + data: refUrl, + source: 'parent_post', + parentPostId, + sourceImageUrl: sourceImageUrl || refUrl, + isPrimary: false, + createdAt: Date.now(), + }); + if (!state.referenceImages.some((item) => item.isPrimary)) { + state.referenceImages[0].isPrimary = true; + } + normalizeReferenceOrder(); + renderReferenceStrip(); + } + } + } if (!silent) { - toast('已载入 parentPostId,可直接继续编辑', 'success'); + toast(addToReferences ? '已载入 parentPostId,并加入参考图' : '已载入 parentPostId,可直接继续编辑', 'success'); } return true; } @@ -324,20 +360,12 @@ function getPrimaryReference() { if (!state.referenceImages.length) return null; - return state.referenceImages.find((item) => item.isPrimary) || state.referenceImages[0]; + return state.referenceImages[0]; } function normalizeReferenceOrder() { if (!state.referenceImages.length) return; - const primary = getPrimaryReference(); - const primaryId = primary ? primary.id : ''; - state.referenceImages = state.referenceImages.map((item) => ({ - ...item, - isPrimary: item.id === primaryId, - })); state.referenceImages.sort((a, b) => { - if (a.isPrimary && !b.isPrimary) return -1; - if (!a.isPrimary && b.isPrimary) return 1; return a.createdAt - b.createdAt; }); } @@ -361,8 +389,8 @@ state.referenceImages.forEach((item) => { const card = document.createElement('div'); - card.className = `reference-item${item.isPrimary ? ' is-primary' : ''}`; - card.title = item.isPrimary ? `主图:${item.name}` : `设为主图:${item.name}`; + card.className = 'reference-item'; + card.title = `预览:${item.name}`; card.dataset.id = item.id; card.setAttribute('role', 'button'); card.setAttribute('tabindex', '0'); @@ -374,13 +402,6 @@ img.loading = 'lazy'; card.appendChild(img); - if (item.isPrimary) { - const badge = document.createElement('div'); - badge.className = 'reference-primary-badge'; - badge.textContent = '主图'; - card.appendChild(badge); - } - const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'reference-remove-btn'; @@ -395,12 +416,12 @@ card.appendChild(removeBtn); card.addEventListener('click', () => { - setPrimaryReference(item.id); + previewReference(item.id); }); card.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); - setPrimaryReference(item.id); + previewReference(item.id); } }); referenceStrip.appendChild(card); @@ -418,26 +439,44 @@ updateReferenceSummary(); } - function setPrimaryReference(id) { + function previewReference(id) { if (!id) return; - let changed = false; - let primaryData = ''; - state.referenceImages = state.referenceImages.map((item) => { - const isPrimary = item.id === id; - if (isPrimary) { - primaryData = String(item.data || ''); + const hit = state.referenceImages.find((item) => item.id === id); + const previewData = String(hit && hit.data ? hit.data : '').trim(); + if (!previewData) return; + + // 仅存在这张 parentPostId 参考图时,点击即恢复到 parentPostId 编辑模式 + if ( + hit + && hit.source === 'parent_post' + && String(hit.parentPostId || '').trim() + && state.referenceImages.length === 1 + ) { + state.currentParentPostId = String(hit.parentPostId || '').trim(); + state.currentSourceImageUrl = String(hit.sourceImageUrl || previewData).trim(); + state.currentModeValue = 'parent_post'; + if (parentPostInput) { + parentPostInput.value = state.currentParentPostId; } - const next = { ...item, isPrimary }; - if (next.isPrimary !== item.isPrimary) changed = true; - return next; - }); - if (!changed) return; - normalizeReferenceOrder(); - renderReferenceStrip(); - if (!state.currentParentPostId && primaryData) { - setPreview(primaryData); - setStatus('', '已切换主图'); + updateMeta(); + rememberParentPost({ + parentPostId: state.currentParentPostId, + sourceImageUrl: state.currentSourceImageUrl, + imageUrl: previewData, + origin: 'workbench_reference_restore', + }); + setPreview(previewData); + setStatus('done', `已恢复 parentPostId 模式:${shortId(state.currentParentPostId)}`); + return; } + + setPreview(previewData); + setStatus('', '已预览参考图'); + } + + function setPrimaryReference(id) { + // 兼容旧调用,点击行为仅预览,不再改变主图 + previewReference(id); } function removeReferenceImage(id) { @@ -510,9 +549,6 @@ }); added += 1; } - if (added > 0 && !state.referenceImages.some((item) => item.isPrimary)) { - state.referenceImages[0].isPrimary = true; - } normalizeReferenceOrder(); renderReferenceStrip(); @@ -1021,7 +1057,7 @@ if (applyParentBtn) { applyParentBtn.addEventListener('click', () => { - applyParentPostFromText(parentPostInput ? parentPostInput.value : ''); + applyParentPostFromText(parentPostInput ? parentPostInput.value : '', { addToReferences: true }); }); } @@ -1029,7 +1065,7 @@ parentPostInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); - applyParentPostFromText(parentPostInput.value); + applyParentPostFromText(parentPostInput.value, { addToReferences: true }); } }); parentPostInput.addEventListener('input', () => {