feat: add PhotoRoom background removal support in RMBG settings
This commit is contained in:
parent
48a8d2e1ff
commit
ab583c38ef
@ -372,8 +372,11 @@ export function AdminSettingsClient() {
|
||||
|
||||
// 抠图API配置状态
|
||||
const [rmbgConfig, setRmbgConfig] = useState({
|
||||
provider: "rmbg" as "rmbg" | "photoroom", // 抠图服务提供商
|
||||
apiUrl: "",
|
||||
apiKey: "",
|
||||
// PhotoRoom 配置
|
||||
photoroomApiKey: "",
|
||||
});
|
||||
|
||||
// 变清晰API配置状态
|
||||
@ -420,10 +423,17 @@ export function AdminSettingsClient() {
|
||||
const { data: rmbgConfigData } = useSWR("/api/admin/settings?key=rmbg_api_config", fetcher);
|
||||
useEffect(() => {
|
||||
if (rmbgConfigData?.setting?.value) {
|
||||
const config = rmbgConfigData.setting.value as { apiUrl?: string; apiKey?: string };
|
||||
const config = rmbgConfigData.setting.value as {
|
||||
provider?: "rmbg" | "photoroom";
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
photoroomApiKey?: string;
|
||||
};
|
||||
setRmbgConfig({
|
||||
provider: config.provider || "rmbg",
|
||||
apiUrl: config.apiUrl || "",
|
||||
apiKey: config.apiKey || "",
|
||||
photoroomApiKey: config.photoroomApiKey || "",
|
||||
});
|
||||
}
|
||||
}, [rmbgConfigData]);
|
||||
@ -3423,44 +3433,107 @@ export function AdminSettingsClient() {
|
||||
<CardHeader>
|
||||
<CardTitle>抠图API配置</CardTitle>
|
||||
<CardDescription>
|
||||
配置RMBG-2.0背景移除API,用于画图标注功能中的抠图处理
|
||||
配置背景移除API,用于画图标注功能中的抠图处理。支持自部署RMBG和PhotoRoom云服务。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 pt-6">
|
||||
{/* 服务提供商选择 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="rmbgApiUrl">API地址 *</Label>
|
||||
<Input
|
||||
id="rmbgApiUrl"
|
||||
value={rmbgConfig.apiUrl}
|
||||
onChange={(e) => setRmbgConfig({ ...rmbgConfig, apiUrl: e.target.value })}
|
||||
placeholder="http://156.225.27.52:8000"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
请输入完整的API地址,例如: http://156.225.27.52:8000
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="rmbgApiKey">API密钥(可选)</Label>
|
||||
<Input
|
||||
id="rmbgApiKey"
|
||||
type="password"
|
||||
value={rmbgConfig.apiKey}
|
||||
onChange={(e) => setRmbgConfig({ ...rmbgConfig, apiKey: e.target.value })}
|
||||
placeholder="如果API需要密钥,请在此输入"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
如果API需要认证,请填写API密钥(可选)
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 space-y-2">
|
||||
<div className="text-sm font-medium text-blue-900">API文档说明</div>
|
||||
<div className="text-xs text-blue-800 space-y-1">
|
||||
<p>• API地址应指向RMBG-2.0服务的根地址</p>
|
||||
<p>• 系统会自动调用 /rmbg/base64 接口进行抠图处理</p>
|
||||
<p>• 支持的图片格式:JPG, PNG, JPEG等常见格式</p>
|
||||
<p>• 输出格式:PNG格式的透明背景图片</p>
|
||||
<Label>抠图服务提供商</Label>
|
||||
<div className="flex gap-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="rmbgProvider"
|
||||
value="rmbg"
|
||||
checked={rmbgConfig.provider === "rmbg"}
|
||||
onChange={() => setRmbgConfig({ ...rmbgConfig, provider: "rmbg" })}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="text-sm">RMBG-2.0 (自部署)</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="rmbgProvider"
|
||||
value="photoroom"
|
||||
checked={rmbgConfig.provider === "photoroom"}
|
||||
onChange={() => setRmbgConfig({ ...rmbgConfig, provider: "photoroom" })}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="text-sm">PhotoRoom (云服务)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* RMBG 配置 */}
|
||||
{rmbgConfig.provider === "rmbg" && (
|
||||
<>
|
||||
<div className="text-sm font-medium text-muted-foreground">RMBG-2.0 配置</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="rmbgApiUrl">API地址 *</Label>
|
||||
<Input
|
||||
id="rmbgApiUrl"
|
||||
value={rmbgConfig.apiUrl}
|
||||
onChange={(e) => setRmbgConfig({ ...rmbgConfig, apiUrl: e.target.value })}
|
||||
placeholder="http://156.225.27.52:8000"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
请输入完整的API地址,例如: http://156.225.27.52:8000
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="rmbgApiKey">API密钥(可选)</Label>
|
||||
<Input
|
||||
id="rmbgApiKey"
|
||||
type="password"
|
||||
value={rmbgConfig.apiKey}
|
||||
onChange={(e) => setRmbgConfig({ ...rmbgConfig, apiKey: e.target.value })}
|
||||
placeholder="如果API需要密钥,请在此输入"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 space-y-2">
|
||||
<div className="text-sm font-medium text-blue-900">RMBG-2.0 说明</div>
|
||||
<div className="text-xs text-blue-800 space-y-1">
|
||||
<p>• 需要自行部署RMBG-2.0服务</p>
|
||||
<p>• 系统会自动调用 /rmbg/base64 接口进行抠图处理</p>
|
||||
<p>• 支持的图片格式:JPG, PNG, JPEG等常见格式</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* PhotoRoom 配置 */}
|
||||
{rmbgConfig.provider === "photoroom" && (
|
||||
<>
|
||||
<div className="text-sm font-medium text-muted-foreground">PhotoRoom 配置</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="photoroomApiKey">API密钥 *</Label>
|
||||
<Input
|
||||
id="photoroomApiKey"
|
||||
type="password"
|
||||
value={rmbgConfig.photoroomApiKey}
|
||||
onChange={(e) => setRmbgConfig({ ...rmbgConfig, photoroomApiKey: e.target.value })}
|
||||
placeholder="输入PhotoRoom API密钥"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
在 PhotoRoom 开发者平台获取API密钥
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-green-200 bg-green-50 p-4 space-y-2">
|
||||
<div className="text-sm font-medium text-green-900">PhotoRoom 说明</div>
|
||||
<div className="text-xs text-green-800 space-y-1">
|
||||
<p>• 使用 PhotoRoom 云服务进行高清抠图</p>
|
||||
<p>• 支持 HD 高清背景移除,效果更好</p>
|
||||
<p>• API文档:https://www.photoroom.com/api</p>
|
||||
<p>• 按调用次数计费,请注意用量</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button onClick={handleSaveRmbgConfig}>
|
||||
<Save className="mr-2 h-4 w-4" /> 保存配置
|
||||
</Button>
|
||||
|
||||
@ -31,13 +31,10 @@ async function createThumbnail(buffer: Buffer, mimeType: string): Promise<Buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 base64 图片上传到 S3,返回缩略图和原图的 URL
|
||||
* 将图片 buffer 上传到 S3,返回缩略图和原图的 URL
|
||||
*/
|
||||
async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> {
|
||||
async function uploadBufferToS3(buffer: Buffer, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> {
|
||||
try {
|
||||
// 解析 base64 数据
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
|
||||
// 生成文件名
|
||||
const ext = mimeType.split("/")[1] || "png";
|
||||
const timestamp = Date.now();
|
||||
@ -81,10 +78,146 @@ async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/pn
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 base64 图片上传到 S3,返回缩略图和原图的 URL
|
||||
*/
|
||||
async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> {
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
return uploadBufferToS3(buffer, mimeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 RMBG-2.0 API 进行抠图
|
||||
*/
|
||||
async function processWithRMBG(
|
||||
imageBase64: string,
|
||||
config: { apiUrl: string; apiKey?: string }
|
||||
): Promise<{ success: boolean; image_base64?: string; processing_time?: number; error?: string }> {
|
||||
const apiUrl = config.apiUrl.endsWith("/")
|
||||
? `${config.apiUrl}rmbg/base64`
|
||||
: `${config.apiUrl}/rmbg/base64`;
|
||||
|
||||
const requestBody = {
|
||||
image_base64: imageBase64,
|
||||
};
|
||||
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
if (config.apiKey) {
|
||||
headers["X-API-Key"] = config.apiKey;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// 添加超时控制(30秒)
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
const processingTime = (Date.now() - startTime) / 1000;
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("[RMBG API] 请求失败:", { status: response.status, error: errorText });
|
||||
return { success: false, error: errorText || response.statusText };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success || !result.image_base64) {
|
||||
return { success: false, error: result.detail || "未知错误" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
image_base64: result.image_base64,
|
||||
processing_time: result.processing_time || processingTime,
|
||||
};
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return { success: false, error: "请求超时(30秒)" };
|
||||
}
|
||||
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 PhotoRoom API 进行抠图
|
||||
*/
|
||||
async function processWithPhotoRoom(
|
||||
imageBuffer: Buffer,
|
||||
apiKey: string
|
||||
): Promise<{ success: boolean; imageBuffer?: Buffer; processing_time?: number; error?: string }> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构建 FormData
|
||||
const formData = new FormData();
|
||||
// 将 Buffer 转换为 Uint8Array 以避免类型问题
|
||||
const uint8Array = new Uint8Array(imageBuffer);
|
||||
const blob = new Blob([uint8Array], { type: "image/png" });
|
||||
formData.append("imageFile", blob, "image.png");
|
||||
formData.append("referenceBox", "originalImage");
|
||||
|
||||
console.log("[PhotoRoom] Sending request to API...");
|
||||
|
||||
// 添加超时控制(60秒,PhotoRoom可能需要更长时间)
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000);
|
||||
|
||||
const response = await fetch("https://image-api.photoroom.com/v2/edit", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
"pr-hd-background-removal": "auto",
|
||||
},
|
||||
body: formData,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
const processingTime = (Date.now() - startTime) / 1000;
|
||||
|
||||
console.log("[PhotoRoom] Response status:", response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("[PhotoRoom API] 请求失败:", { status: response.status, error: errorText });
|
||||
return { success: false, error: `PhotoRoom API错误: ${response.status} - ${errorText}` };
|
||||
}
|
||||
|
||||
// PhotoRoom 直接返回图片二进制数据
|
||||
const resultBuffer = Buffer.from(await response.arrayBuffer());
|
||||
console.log("[PhotoRoom] Received image buffer, size:", resultBuffer.length, "bytes");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
imageBuffer: resultBuffer,
|
||||
processing_time: processingTime,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return { success: false, error: "PhotoRoom请求超时(60秒)" };
|
||||
}
|
||||
console.error("[PhotoRoom] Error:", error);
|
||||
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抠图API路由
|
||||
* 从数据库读取RMBG API配置,调用外部API进行抠图处理
|
||||
* 将处理后的图片上传到S3并保存到数据库
|
||||
* 从数据库读取API配置,支持 RMBG-2.0 和 PhotoRoom 两种服务
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -98,7 +231,8 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const userId = session.user.id;
|
||||
// 1. 从数据库获取RMBG API配置
|
||||
|
||||
// 1. 从数据库获取抠图API配置
|
||||
const rmbgConfig = await getSystemSetting({ key: "rmbg_api_config" });
|
||||
|
||||
if (!rmbgConfig || !rmbgConfig.value) {
|
||||
@ -109,24 +243,37 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const config = rmbgConfig.value as {
|
||||
provider?: "rmbg" | "photoroom";
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
photoroomApiKey?: string;
|
||||
};
|
||||
|
||||
if (!config.apiUrl) {
|
||||
const provider = config.provider || "rmbg";
|
||||
|
||||
// 验证配置
|
||||
if (provider === "rmbg" && !config.apiUrl) {
|
||||
return NextResponse.json(
|
||||
{ error: "抠图API地址未配置" },
|
||||
{ error: "RMBG API地址未配置" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (provider === "photoroom" && !config.photoroomApiKey) {
|
||||
return NextResponse.json(
|
||||
{ error: "PhotoRoom API密钥未配置" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 获取请求体(支持 imageUrl、image_base64 或 FormData)
|
||||
// 2. 获取请求体
|
||||
const contentType = request.headers.get("content-type") || "";
|
||||
|
||||
let imageBuffer: Buffer;
|
||||
let imageBase64: string;
|
||||
let prompt = ""; // 用户提示词(可选)
|
||||
let chatId: string | null = null; // 聊天ID(可选)
|
||||
let messageId: string | null = null; // 消息ID(可选)
|
||||
let prompt = "";
|
||||
let chatId: string | null = null;
|
||||
let messageId: string | null = null;
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
const body = await request.json();
|
||||
@ -134,9 +281,7 @@ export async function POST(request: NextRequest) {
|
||||
chatId = body.chatId || null;
|
||||
messageId = body.messageId || null;
|
||||
|
||||
// 优先使用 imageUrl,其次使用 image_base64
|
||||
if (body.imageUrl) {
|
||||
// 从 URL 获取图片并转换为 Base64
|
||||
console.log(`[RMBG] 从 URL 获取图片: ${body.imageUrl}`);
|
||||
try {
|
||||
const imageResponse = await fetch(body.imageUrl);
|
||||
@ -147,9 +292,9 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = buffer.toString("base64");
|
||||
console.log(`[RMBG] 图片已转换为 Base64,大小: ${buffer.length} bytes`);
|
||||
imageBuffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = imageBuffer.toString("base64");
|
||||
console.log(`[RMBG] 图片已获取,大小: ${imageBuffer.length} bytes`);
|
||||
} catch (fetchError) {
|
||||
console.error("[RMBG] 获取图片失败:", fetchError);
|
||||
return NextResponse.json(
|
||||
@ -159,6 +304,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
} else if (body.image_base64) {
|
||||
imageBase64 = body.image_base64;
|
||||
imageBuffer = Buffer.from(imageBase64, "base64");
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: "缺少 imageUrl 或 image_base64 参数" },
|
||||
@ -166,7 +312,6 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
} else if (contentType.includes("multipart/form-data")) {
|
||||
// FormData格式:文件上传
|
||||
const formData = await request.formData();
|
||||
const file = formData.get("file") as File;
|
||||
|
||||
@ -177,10 +322,9 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// 将文件转换为Base64
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = buffer.toString("base64");
|
||||
imageBuffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = imageBuffer.toString("base64");
|
||||
prompt = (formData.get("prompt") as string) || "抠图处理";
|
||||
chatId = (formData.get("chatId") as string) || null;
|
||||
messageId = (formData.get("messageId") as string) || null;
|
||||
@ -191,134 +335,52 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 调用RMBG API
|
||||
const apiUrl = config.apiUrl.endsWith("/")
|
||||
? `${config.apiUrl}rmbg/base64`
|
||||
: `${config.apiUrl}/rmbg/base64`;
|
||||
|
||||
const requestBody = {
|
||||
image_base64: imageBase64,
|
||||
};
|
||||
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
// 如果配置了API Key,添加到请求头(根据RMBG API文档要求使用X-API-Key)
|
||||
if (config.apiKey) {
|
||||
headers["X-API-Key"] = config.apiKey;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
let response: Response;
|
||||
// 3. 根据提供商调用不同的API
|
||||
console.log(`[RMBG] Using provider: ${provider}`);
|
||||
|
||||
try {
|
||||
// 添加超时控制(30秒)
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||
let resultBuffer: Buffer;
|
||||
let resultBase64: string;
|
||||
let processingTime: number;
|
||||
|
||||
if (provider === "photoroom") {
|
||||
// 使用 PhotoRoom API
|
||||
const result = await processWithPhotoRoom(imageBuffer, config.photoroomApiKey!);
|
||||
|
||||
response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
} catch (fetchError) {
|
||||
const processingTime = (Date.now() - startTime) / 1000;
|
||||
console.error("[RMBG API] 网络请求失败:", {
|
||||
error: fetchError,
|
||||
apiUrl,
|
||||
processingTime,
|
||||
});
|
||||
|
||||
// 检查是否是网络连接错误
|
||||
if (fetchError instanceof Error) {
|
||||
// 检查超时错误
|
||||
if (fetchError.name === 'AbortError') {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "抠图API请求超时",
|
||||
detail: "请求超过30秒未响应,请检查API服务器状态或网络连接",
|
||||
},
|
||||
{ status: 504 }
|
||||
);
|
||||
}
|
||||
|
||||
// 检查错误消息和cause中的网络错误
|
||||
const errorMessage = fetchError.message || '';
|
||||
const errorCause = (fetchError as any).cause;
|
||||
const causeMessage = errorCause?.message || '';
|
||||
const causeCode = errorCause?.code || '';
|
||||
|
||||
// 检查各种网络连接错误
|
||||
if (errorMessage.includes('EHOSTUNREACH') ||
|
||||
errorMessage.includes('ENOTFOUND') ||
|
||||
errorMessage.includes('ECONNREFUSED') ||
|
||||
errorMessage.includes('No route to host') ||
|
||||
causeMessage.includes('EHOSTUNREACH') ||
|
||||
causeMessage.includes('ENOTFOUND') ||
|
||||
causeMessage.includes('ECONNREFUSED') ||
|
||||
causeMessage.includes('No route to host') ||
|
||||
causeCode === 'EHOSTUNREACH' ||
|
||||
causeCode === 'ENOTFOUND' ||
|
||||
causeCode === 'ECONNREFUSED') {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "无法连接到抠图API服务器",
|
||||
detail: `服务器无法访问 ${apiUrl},请检查:\n1. API服务器是否在线\n2. 网络连接是否正常\n3. 防火墙设置是否正确\n4. 端口8000是否已开放`,
|
||||
},
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
if (!result.success || !result.imageBuffer) {
|
||||
return NextResponse.json(
|
||||
{ error: "抠图处理失败", detail: result.error },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "抠图API请求失败",
|
||||
detail: fetchError instanceof Error ? fetchError.message : "未知网络错误",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const processingTime = (Date.now() - startTime) / 1000;
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("[RMBG API] 请求失败:", {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
apiUrl,
|
||||
resultBuffer = result.imageBuffer;
|
||||
resultBase64 = resultBuffer.toString("base64");
|
||||
processingTime = result.processing_time || 0;
|
||||
} else {
|
||||
// 使用 RMBG-2.0 API
|
||||
const result = await processWithRMBG(imageBase64, {
|
||||
apiUrl: config.apiUrl!,
|
||||
apiKey: config.apiKey,
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "抠图API请求失败",
|
||||
detail: errorText || response.statusText,
|
||||
},
|
||||
{ status: response.status }
|
||||
);
|
||||
if (!result.success || !result.image_base64) {
|
||||
return NextResponse.json(
|
||||
{ error: "抠图处理失败", detail: result.error },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
resultBase64 = result.image_base64;
|
||||
resultBuffer = Buffer.from(resultBase64, "base64");
|
||||
processingTime = result.processing_time || 0;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 4. 处理返回结果
|
||||
if (!result.success || !result.image_base64) {
|
||||
return NextResponse.json(
|
||||
{ error: "抠图处理失败", detail: result.detail || "未知错误" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 上传处理后的图片到S3存储桶
|
||||
// 4. 上传处理后的图片到S3
|
||||
let thumbnailUrl: string;
|
||||
let originalUrl: string;
|
||||
|
||||
try {
|
||||
const uploadResult = await uploadBase64ToS3(result.image_base64, "image/png");
|
||||
const uploadResult = await uploadBufferToS3(resultBuffer, "image/png");
|
||||
thumbnailUrl = uploadResult.thumbnail;
|
||||
originalUrl = uploadResult.original;
|
||||
console.log(`[RMBG] ✅ Images uploaded to S3 successfully`);
|
||||
@ -333,7 +395,7 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// 6. 保存图片记录到数据库(相册和生图记录)
|
||||
// 5. 保存图片记录到数据库
|
||||
try {
|
||||
const [savedImage] = await db.insert(generatedImage).values({
|
||||
userId,
|
||||
@ -341,31 +403,23 @@ export async function POST(request: NextRequest) {
|
||||
messageId: messageId || null,
|
||||
thumbnailUrl,
|
||||
originalUrl,
|
||||
imageSize: "2K", // 默认尺寸
|
||||
imageSize: "2K",
|
||||
prompt: prompt || "抠图处理",
|
||||
modelId: "rmbg-2.0", // 标记为抠图处理
|
||||
modelId: provider === "photoroom" ? "photoroom-hd" : "rmbg-2.0",
|
||||
}).returning();
|
||||
console.log(`[RMBG] ✅ 图片已保存到相册和生图记录,ID: ${savedImage.id}`);
|
||||
console.log(`[RMBG] ✅ 图片已保存到相册,ID: ${savedImage.id}`);
|
||||
} catch (dbError) {
|
||||
console.error("[RMBG] ❌ 保存到数据库失败:", dbError);
|
||||
// 数据库保存失败时仍然返回成功,但记录错误(图片已上传到S3,用户可以访问)
|
||||
// 如果希望严格保证数据一致性,可以取消注释下面的代码来返回错误
|
||||
// return NextResponse.json(
|
||||
// {
|
||||
// error: "图片已处理但保存到相册失败",
|
||||
// detail: dbError instanceof Error ? dbError.message : "未知错误"
|
||||
// },
|
||||
// { status: 500 }
|
||||
// );
|
||||
}
|
||||
|
||||
// 7. 返回结果(包含S3 URL和Base64,前端可以选择使用)
|
||||
// 6. 返回结果
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
image_base64: result.image_base64, // 保留Base64用于前端立即显示
|
||||
thumbnail_url: thumbnailUrl, // S3缩略图URL
|
||||
original_url: originalUrl, // S3原图URL
|
||||
processing_time: result.processing_time || processingTime,
|
||||
image_base64: resultBase64,
|
||||
thumbnail_url: thumbnailUrl,
|
||||
original_url: originalUrl,
|
||||
processing_time: processingTime,
|
||||
provider: provider,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[RMBG API] 处理错误:", error);
|
||||
@ -375,4 +429,3 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user