feat: add watermark removal to detail results

This commit is contained in:
Your Name 2026-06-05 17:58:22 +08:00
parent 2b82d57f73
commit 528968d74d

View File

@ -18,6 +18,7 @@ import { motion, AnimatePresence } from "framer-motion";
import {
ArrowUp,
CheckCircle2,
Droplet,
RefreshCw,
ChevronDown,
ChevronUp,
@ -67,6 +68,7 @@ import { useAgentStylePerformanceMonitor } from "@/components/agents/use-agent-s
import { ImageAnnotator } from "@/components/image-annotator";
import { useCanvasStore } from "@/store/canvas";
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
import { requestLongTaskResult } from "@/lib/client/long-task";
import { buildImageProxyUrl } from "@/lib/image-delivery";
import { extractImageUrlsFromText } from "@/lib/image-url";
import {
@ -1457,6 +1459,8 @@ export function AgentGenerationWorkspaceDarkV2({
const [replacementRerunTarget, setReplacementRerunTarget] =
useState<ReplacementRerunTarget | null>(null);
const [replacementRerunPrompt, setReplacementRerunPrompt] = useState("");
const [detailWatermarkRemoveCardIds, setDetailWatermarkRemoveCardIds] =
useState<Set<string>>(() => new Set());
const closePsdTemplateDialog = useCallback(() => {
setIsPsdTemplateDialogOpen(false);
setPsdTemplateDialogSourceUrl(null);
@ -3859,6 +3863,74 @@ export function AgentGenerationWorkspaceDarkV2({
title: String(params.title || "").trim() || "待改字结果",
});
}, []);
const handleRemoveDetailResultWatermark = useCallback(
async (params: {
cardId: string;
imageUrl: string;
}) => {
const rawUrl = String(params.imageUrl || "").trim();
const cardId = String(params.cardId || "").trim();
if (!cardId || !/^https?:\/\//i.test(rawUrl)) {
toast.error("结果图地址无效,无法去水印");
return;
}
if (isAnyGenerationRunning) {
toast.info("当前仍有生成任务处理中,完成后再去水印");
return;
}
if (detailWatermarkRemoveCardIds.has(cardId)) {
return;
}
setDetailWatermarkRemoveCardIds((current) => {
const next = new Set(current);
next.add(cardId);
return next;
});
toast.info("正在去水印...");
try {
const payload = await requestLongTaskResult({
endpoint: "/api/watermark-remove",
payload: {
image_url: rawUrl,
},
});
if (!payload?.success) {
throw new Error(payload?.error || payload?.detail || "去水印失败");
}
const nextUrl = String(payload.original_url || payload.thumbnail_url || "").trim();
if (!/^https?:\/\//i.test(nextUrl)) {
throw new Error("去水印完成但结果地址无效");
}
handleUpdateReplacementResultRecord({
modeType: "detail_replacement",
cardId,
url: nextUrl,
text: "已完成去水印处理。",
status: "completed",
});
setActiveResultModeType("detail_replacement");
setSelectedResultUrl(nextUrl);
toast.success("去水印完成,已更新当前结果图");
} catch (error) {
console.error("[AgentGenerationWorkspaceDarkV2] 去水印失败:", error);
toast.error(error instanceof Error ? error.message : "去水印失败,请稍后重试");
} finally {
setDetailWatermarkRemoveCardIds((current) => {
const next = new Set(current);
next.delete(cardId);
return next;
});
}
},
[
detailWatermarkRemoveCardIds,
handleUpdateReplacementResultRecord,
isAnyGenerationRunning,
]
);
const getReplacementExecutionCards = useCallback(
(params: {
modeType: ReplacementModeType;
@ -7057,6 +7129,7 @@ export function AgentGenerationWorkspaceDarkV2({
const statusMeta = getReplacementCardStatusMeta(card.status);
const isPending = card.status === "generating" || card.status === "queued";
const hasResult = Boolean(card.resultUrl) && !isPending;
const isWatermarkRemoving = detailWatermarkRemoveCardIds.has(card.id);
return (
<div
key={`${card.id}-result`}
@ -7144,6 +7217,27 @@ export function AgentGenerationWorkspaceDarkV2({
>
重新生图
</button>
<button
type="button"
disabled={isAnyGenerationRunning || isWatermarkRemoving}
onClick={() =>
void handleRemoveDetailResultWatermark({
cardId: card.id,
imageUrl: card.resultUrl!,
})
}
className={cn(
IMAGE_OVERLAY_ACTION_CLASS,
"inline-flex items-center gap-1.5 disabled:cursor-not-allowed disabled:opacity-45"
)}
>
{isWatermarkRemoving ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Droplet className="h-3.5 w-3.5" />
)}
<span>{isWatermarkRemoving ? "去水印中" : "去水印"}</span>
</button>
</div>
</div>
) : (