feat: imagine-workbench 多图结果与历史交互优化

This commit is contained in:
XianYuDaXian committed 2026-02-28 18:34:53 +08:00
1 parent cc47603ca2
commit 4ef2ad0db1
5 files changed
+124 -37

No files matched your search

+8 -2
View File
@@ -1084,6 +1084,7 @@ async def public_imagine_workbench_edit(data: ImagineWorkbenchEditRequest, reque
source_image_url=source_image_url,
response_format="url",
stream=False,
return_all_images=True,
progress_cb=progress_cb,
)
mode = "parent_post"
@@ -1104,6 +1105,7 @@ async def public_imagine_workbench_edit(data: ImagineWorkbenchEditRequest, reque
n=1,
response_format="url",
stream=False,
return_all_images=True,
progress_cb=progress_cb,
)
mode = "upload"
@@ -1112,7 +1114,11 @@ async def public_imagine_workbench_edit(data: ImagineWorkbenchEditRequest, reque
if not images:
raise HTTPException(status_code=502, detail="Image edit returned no results")
image_url = str(images[0])
normalized_images = [str(item or "").strip() for item in images if str(item or "").strip()]
if not normalized_images:
raise HTTPException(status_code=502, detail="Image edit returned no results")
image_url = normalized_images[0]
generated_parent_post_id = _extract_parent_post_id_from_url(image_url)
current_parent_post_id = generated_parent_post_id or parent_post_id
if current_parent_post_id:
@@ -1126,7 +1132,7 @@ async def public_imagine_workbench_edit(data: ImagineWorkbenchEditRequest, reque
return {
"created": int(time.time()),
"data": [{"url": image_url}],
"data": [{"url": url} for url in normalized_images],
"mode": mode,
"input_parent_post_id": parent_post_id if use_parent_mode else "",
"generated_parent_post_id": generated_parent_post_id,
+7
View File
@@ -135,6 +135,7 @@ class ImageEditService:
n: int,
response_format: str,
stream: bool,
return_all_images: bool = False,
progress_cb: Callable[[str, dict], Any] | None = None,
) -> ImageEditResult:
max_token_retries = int(get_config("retry.max_retry"))
@@ -251,6 +252,7 @@ class ImageEditService:
response_format=response_format,
tool_overrides=tool_overrides,
model_config_override=model_config_override,
return_all_images=return_all_images,
progress_cb=progress_cb,
)
await self._emit_progress(
@@ -310,6 +312,7 @@ class ImageEditService:
source_image_url: str,
response_format: str,
stream: bool,
return_all_images: bool = False,
progress_cb: Callable[[str, dict], Any] | None = None,
) -> ImageEditResult:
"""基于 parentPostId 进行编辑,不上传图片。"""
@@ -437,6 +440,7 @@ class ImageEditService:
response_format=response_format,
tool_overrides=tool_overrides,
model_config_override=model_config_override,
return_all_images=return_all_images,
progress_cb=progress_cb,
)
await self._emit_progress(
@@ -567,6 +571,7 @@ class ImageEditService:
response_format: str,
tool_overrides: dict,
model_config_override: dict,
return_all_images: bool = False,
progress_cb: Callable[[str, dict], Any] | None = None,
) -> List[str]:
async def _call_edit():
@@ -594,6 +599,8 @@ class ImageEditService:
raise UpstreamException(
"Image edit returned no results", details={"error": "empty_result"}
)
if return_all_images:
return all_images
return [all_images[0]]
+33 -3
View File
@@ -216,17 +216,47 @@ body {
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.22);
}
.current-image {
.current-gallery {
width: 100%;
display: block;
height: 100%;
min-height: 240px;
display: grid;
grid-template-columns: 1fr;
gap: 8px;
padding: 8px;
box-sizing: border-box;
position: relative;
z-index: 1;
}
.current-image.hidden {
.current-gallery.hidden {
display: none;
}
.current-gallery[data-count="2"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.current-gallery[data-count="3"],
.current-gallery[data-count="4"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.current-gallery-item {
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--workbench-border);
background: #0b1220;
min-height: 210px;
}
.current-gallery-item img {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
}
.preview-empty {
color: var(--workbench-muted);
font-size: 13px;
+75 -31
View File
@@ -6,7 +6,7 @@
const parentPostInput = document.getElementById('parentPostInput');
const applyParentBtn = document.getElementById('applyParentBtn');
const previewShell = document.getElementById('previewShell');
const currentImage = document.getElementById('currentImage');
const currentGallery = document.getElementById('currentGallery');
const previewEmpty = document.getElementById('previewEmpty');
const currentParentId = document.getElementById('currentParentId');
const currentMode = document.getElementById('currentMode');
@@ -27,6 +27,7 @@
editing: false,
referenceImages: [],
currentImageUrl: '',
currentImageUrls: [],
currentParentPostId: '',
currentSourceImageUrl: '',
currentModeValue: 'upload',
@@ -587,21 +588,47 @@
updateReferenceSummary();
}
function setPreview(url) {
const displayUrl = String(url || '').trim();
state.currentImageUrl = displayUrl;
if (!currentImage || !previewEmpty) return;
if (!displayUrl) {
currentImage.src = '';
currentImage.classList.add('hidden');
function setPreviewImages(urls) {
const list = (Array.isArray(urls) ? urls : [])
.map((item) => String(item || '').trim())
.filter(Boolean);
state.currentImageUrls = list;
state.currentImageUrl = list[0] || '';
if (!currentGallery || !previewEmpty) return;
currentGallery.innerHTML = '';
const primaryUrl = list[0] || '';
if (!primaryUrl) {
currentGallery.dataset.count = '0';
currentGallery.classList.add('hidden');
previewEmpty.classList.remove('hidden');
return;
}
currentImage.src = displayUrl;
currentImage.classList.remove('hidden');
currentGallery.dataset.count = '1';
const item = document.createElement('div');
item.className = 'current-gallery-item';
const img = document.createElement('img');
img.src = primaryUrl;
img.alt = 'result-current';
img.loading = 'lazy';
img.decoding = 'async';
item.appendChild(img);
currentGallery.appendChild(item);
currentGallery.classList.remove('hidden');
previewEmpty.classList.add('hidden');
}
function setPreview(url) {
const displayUrl = String(url || '').trim();
if (!displayUrl) {
setPreviewImages([]);
return;
}
setPreviewImages([displayUrl]);
}
function setEditing(loading) {
state.editing = Boolean(loading);
if (submitEditBtn) {
@@ -677,7 +704,10 @@
const line1 = document.createElement('div');
line1.className = 'history-line';
line1.innerHTML = `<strong>#${entry.round}</strong> 路 ${formatTime(entry.createdAt)} 路 ${entry.elapsedMs}ms`;
const roundLabel = entry.roundTotal && entry.roundTotal > 1
? `#${entry.round}-${entry.roundIndex || 1}`
: `#${entry.round}`;
line1.innerHTML = `<strong>${roundLabel}</strong> 路 ${formatTime(entry.createdAt)} 路 ${entry.elapsedMs}ms`;
const line2 = document.createElement('div');
line2.className = 'history-line';
@@ -952,7 +982,12 @@
setStatus('running', message);
}
}, workbenchEditAbortController ? workbenchEditAbortController.signal : undefined);
const imageUrl = String(payload?.data?.[0]?.url || '').trim();
const imageUrls = Array.isArray(payload?.data)
? payload.data
.map((item) => String((item && item.url) || '').trim())
.filter(Boolean)
: [];
const imageUrl = imageUrls[0] || '';
if (!imageUrl) {
throw new Error('返回结果缺少图片 URL');
}
@@ -985,32 +1020,41 @@
if (parentPostInput) {
parentPostInput.value = state.currentParentPostId || '';
}
setPreview(imageUrl);
setPreviewImages(imageUrls);
updateMeta();
state.editRound += 1;
const entry = {
id: `${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
round: state.editRound,
prompt,
mode: state.currentModeValue,
imageUrl,
parentPostId: resolvedParentPostId,
sourceImageUrl,
elapsedMs: Number.isFinite(elapsedMs) ? Math.max(0, Math.round(elapsedMs)) : 0,
createdAt: Date.now(),
};
state.history.unshift(entry);
const createdAt = Date.now();
const historyEntries = imageUrls.map((url, index) => {
const perParentPostId = String(extractParentPostId(url) || resolvedParentPostId || '').trim();
return {
id: `${createdAt}_${index}_${Math.random().toString(16).slice(2, 8)}`,
round: state.editRound,
roundIndex: index + 1,
roundTotal: imageUrls.length,
prompt,
mode: state.currentModeValue,
imageUrl: url,
imageUrls,
parentPostId: perParentPostId,
sourceImageUrl: String(url || sourceImageUrl || '').trim(),
elapsedMs: Number.isFinite(elapsedMs) ? Math.max(0, Math.round(elapsedMs)) : 0,
createdAt,
};
});
state.history = [...historyEntries, ...state.history];
renderHistory();
rememberParentPost({
parentPostId: resolvedParentPostId,
sourceImageUrl,
imageUrl,
origin: 'workbench_edit',
historyEntries.forEach((entry) => {
rememberParentPost({
parentPostId: entry.parentPostId || resolvedParentPostId,
sourceImageUrl: entry.sourceImageUrl || sourceImageUrl,
imageUrl: entry.imageUrl || imageUrl,
origin: 'workbench_edit',
});
});
finishEditProgress(true, '编辑完成 100%');
setStatus('done', `编辑完成 · round #${entry.round}`);
setStatus('done', `编辑完成 · round #${state.editRound}(${imageUrls.length} 张)`);
toast('编辑成功,已更新当前画面', 'success');
} catch (e) {
if (e && e.name === 'AbortError') {
@@ -63,7 +63,7 @@
</div>
<div id="previewShell" class="preview-shell">
<img id="currentImage" class="current-image hidden" alt="current-image">
<div id="currentGallery" class="current-gallery hidden"></div>
<div id="previewEmpty" class="preview-empty">请先添加参考图</div>
<div id="dropHint" class="drop-hint">支持拖拽图片到此处,或按 Ctrl+V 粘贴图片</div>
</div>