feat: imagine-workbench 多图结果与历史交互优化
This commit is contained in:
1 parent
cc47603ca2
commit
4ef2ad0db1
5 files changed
+124
-37
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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]]
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user