fix: scene fusion retry now correctly handles streaming response and batch download works for all images
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-01-27 21:40:46 +08:00
parent ee4bb14736
commit 0d69e8d9fa
2 changed files with 202 additions and 62 deletions

View File

@ -253,25 +253,76 @@ export function SceneFusionDialog({ open, onOpenChange }: SceneFusionDialogProps
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '处理失败');
// 尝试解析错误信息
try {
const errorData = await response.json();
throw new Error(errorData.error || '处理失败');
} catch {
throw new Error(`请求失败: ${response.status}`);
}
}
const result = data.results?.[0];
if (result?.status === 'completed') {
setScenes(prev => prev.map(s =>
s.id === id ? {
...s,
status: 'completed',
result: result.result,
originalUrl: result.originalUrl,
} : s
));
toast.success('重新生成成功');
} else {
throw new Error(result?.error || '生成失败');
// 处理流式响应
const reader = response.body?.getReader();
if (!reader) {
throw new Error('无法读取响应流');
}
const decoder = new TextDecoder();
let buffer = '';
let success = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
try {
const message = JSON.parse(line);
console.log('[SceneFusion] Regenerate message:', message.type);
if (message.type === 'result') {
const result = message.data;
if (result.id === id) {
if (result.status === 'completed') {
setScenes(prev => prev.map(s =>
s.id === id ? {
...s,
status: 'completed',
result: result.result,
originalUrl: result.originalUrl,
} : s
));
toast.success('重新生成成功');
success = true;
} else {
throw new Error(result.error || '生成失败');
}
}
} else if (message.type === 'error') {
throw new Error(message.error || '处理失败');
}
} catch (parseError) {
if (parseError instanceof SyntaxError) {
console.error('[SceneFusion] Failed to parse message:', line);
} else {
throw parseError;
}
}
}
}
if (!success) {
throw new Error('未收到有效的生成结果');
}
} catch (error) {
console.error('[SceneFusion] Regenerate error:', error);
@ -282,17 +333,30 @@ export function SceneFusionDialog({ open, onOpenChange }: SceneFusionDialogProps
}
};
// 下载图片
const downloadImage = (url: string, filename: string) => {
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 下载图片 - 使用 fetch 获取图片数据后下载,解决跨域问题
const downloadImage = async (url: string, filename: string) => {
try {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放 Blob URL
setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
} catch (error) {
console.error('下载图片失败:', error);
// 降级方案:直接打开链接
window.open(url, '_blank');
}
};
// 全部下载
// 全部下载 - 顺序下载所有图片
const downloadAll = async () => {
const completedScenes = scenes.filter(s => s.status === 'completed' && (s.originalUrl || s.result));
if (completedScenes.length === 0) {
@ -300,16 +364,22 @@ export function SceneFusionDialog({ open, onOpenChange }: SceneFusionDialogProps
return;
}
toast.info(`正在下载 ${completedScenes.length} 张图片...`);
toast.info(`正在下载 ${completedScenes.length} 张图片,请稍候...`);
// 顺序下载,每张图片下载完成后再下载下一张
for (let i = 0; i < completedScenes.length; i++) {
const scene = completedScenes[i];
const url = scene.originalUrl || scene.result!;
// 延迟下载避免浏览器阻止
setTimeout(() => {
downloadImage(url, `scene-fusion-${i + 1}.png`);
}, i * 300);
try {
await downloadImage(url, `scene-fusion-${i + 1}.png`);
// 添加小延迟避免浏览器阻止
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`下载第 ${i + 1} 张图片失败:`, error);
}
}
toast.success(`已下载 ${completedScenes.length} 张图片`);
};
return (

View File

@ -337,8 +337,8 @@ export function SceneFusionView({ onBack }: SceneFusionViewProps) {
body: JSON.stringify({
productImage,
productName: productName || undefined,
aspectRatio, // 图片比例
imageSize, // 图片分辨率
aspectRatio,
imageSize,
scenes: [{
id: scene.id,
image: scene.image,
@ -348,25 +348,76 @@ export function SceneFusionView({ onBack }: SceneFusionViewProps) {
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '处理失败');
// 尝试解析错误信息
try {
const errorData = await response.json();
throw new Error(errorData.error || '处理失败');
} catch {
throw new Error(`请求失败: ${response.status}`);
}
}
const result = data.results?.[0];
if (result?.status === 'completed') {
setScenes(prev => prev.map(s =>
s.id === id ? {
...s,
status: 'completed',
result: result.result,
originalUrl: result.originalUrl,
} : s
));
toast.success('重新生成成功');
} else {
throw new Error(result?.error || '生成失败');
// 处理流式响应
const reader = response.body?.getReader();
if (!reader) {
throw new Error('无法读取响应流');
}
const decoder = new TextDecoder();
let buffer = '';
let success = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
try {
const message = JSON.parse(line);
console.log('[SceneFusion] Regenerate message:', message.type);
if (message.type === 'result') {
const result = message.data;
if (result.id === id) {
if (result.status === 'completed') {
setScenes(prev => prev.map(s =>
s.id === id ? {
...s,
status: 'completed',
result: result.result,
originalUrl: result.originalUrl,
} : s
));
toast.success('重新生成成功');
success = true;
} else {
throw new Error(result.error || '生成失败');
}
}
} else if (message.type === 'error') {
throw new Error(message.error || '处理失败');
}
} catch (parseError) {
if (parseError instanceof SyntaxError) {
console.error('[SceneFusion] Failed to parse message:', line);
} else {
throw parseError;
}
}
}
}
if (!success) {
throw new Error('未收到有效的生成结果');
}
} catch (error) {
console.error('[SceneFusion] Regenerate error:', error);
@ -377,17 +428,30 @@ export function SceneFusionView({ onBack }: SceneFusionViewProps) {
}
};
// 下载图片
const downloadImage = (url: string, filename: string) => {
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 下载图片 - 使用 fetch 获取图片数据后下载,解决跨域问题
const downloadImage = async (url: string, filename: string) => {
try {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放 Blob URL
setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
} catch (error) {
console.error('下载图片失败:', error);
// 降级方案:直接打开链接
window.open(url, '_blank');
}
};
// 全部下载
// 全部下载 - 顺序下载所有图片
const downloadAll = async () => {
const completedScenes = scenes.filter(s => s.status === 'completed' && (s.originalUrl || s.result));
if (completedScenes.length === 0) {
@ -395,16 +459,22 @@ export function SceneFusionView({ onBack }: SceneFusionViewProps) {
return;
}
toast.info(`正在下载 ${completedScenes.length} 张图片...`);
toast.info(`正在下载 ${completedScenes.length} 张图片,请稍候...`);
// 顺序下载,每张图片下载完成后再下载下一张
for (let i = 0; i < completedScenes.length; i++) {
const scene = completedScenes[i];
const url = scene.originalUrl || scene.result!;
// 延迟下载避免浏览器阻止
setTimeout(() => {
downloadImage(url, `scene-fusion-${i + 1}.png`);
}, i * 300);
try {
await downloadImage(url, `scene-fusion-${i + 1}.png`);
// 添加小延迟避免浏览器阻止
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`下载第 ${i + 1} 张图片失败:`, error);
}
}
toast.success(`已下载 ${completedScenes.length} 张图片`);
};
return (