feat(image): integrate PoloX image editing workflows
Add WaveSpeed image editing and image tools plugins, canvas image editing workflows, layer selection, and Cloud Agent image tools with approval-safe transient references.
This commit is contained in:
73
web/src/components/canvas/canvas-node-text-edit-dialog.tsx
Normal file
73
web/src/components/canvas/canvas-node-text-edit-dialog.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, Button, Input, Modal, Spin } from "antd";
|
||||
import { FileText, RefreshCw, WandSparkles, X } from "lucide-react";
|
||||
|
||||
export type CanvasImageTextLine = { original: string; text: string; location: string };
|
||||
export type CanvasImageTextEditPayload = { lines: CanvasImageTextLine[] };
|
||||
|
||||
export function buildCanvasTextEditPrompt(lines: CanvasImageTextLine[]) {
|
||||
const changed = lines.filter((line) => line.text.trim() && line.text !== line.original);
|
||||
return `编辑提供的原图,仅修改指定位置的文字。保持原图构图、字体风格、字号、颜色、排版、材质和其他画面细节不变,不要新增水印或其他文字。${changed.map((line) => `在“${line.location}”位置,将“${line.original}”改为“${line.text}”。`).join("\n")}`;
|
||||
}
|
||||
|
||||
export function CanvasNodeTextEditDialog({
|
||||
dataUrl,
|
||||
open,
|
||||
onClose,
|
||||
onDetect,
|
||||
onConfirm,
|
||||
}: {
|
||||
dataUrl: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onDetect: () => Promise<CanvasImageTextLine[]>;
|
||||
onConfirm: (payload: CanvasImageTextEditPayload) => void;
|
||||
}) {
|
||||
const [lines, setLines] = useState<CanvasImageTextLine[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const detect = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const detected = await onDetect();
|
||||
setLines(detected.map((line) => ({ ...line, text: line.text || line.original })));
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : "文字识别失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setLines([]);
|
||||
void detect();
|
||||
// 只在打开或切换源图时识别,避免输入文字触发重复识别。
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataUrl, open]);
|
||||
|
||||
const changed = lines.some((line) => line.text.trim() && line.text !== line.original);
|
||||
|
||||
return (
|
||||
<Modal open={open && Boolean(dataUrl)} onCancel={onClose} footer={null} centered destroyOnHidden width={980} title="图片文字编辑">
|
||||
<div className="grid gap-5 md:grid-cols-[minmax(0,1fr)_390px]">
|
||||
<div className="grid min-h-[360px] place-items-center overflow-hidden rounded-xl bg-black/5 p-3 dark:bg-white/[0.04]">
|
||||
<img src={dataUrl} alt="待编辑图片" className="max-h-[64vh] max-w-full object-contain" draggable={false} />
|
||||
</div>
|
||||
<div className="flex min-h-[360px] flex-col gap-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">识别图片文字</h3>
|
||||
<p className="mt-1 text-sm opacity-60">逐行修改文字,位置描述只用于帮助模型定位。</p>
|
||||
</div>
|
||||
<Button size="small" icon={<RefreshCw className="size-3.5" />} onClick={() => void detect()} disabled={loading}>重新识别</Button>
|
||||
</div>
|
||||
{loading ? <div className="flex flex-1 items-center justify-center gap-2 text-sm opacity-65"><Spin size="small" />正在识别图片文字…</div> : error ? <div className="space-y-3"><Alert type="error" showIcon message={error} /><Button onClick={() => void detect()}>重试</Button></div> : lines.length ? <div className="min-h-0 flex-1 space-y-3 overflow-y-auto pr-1">{lines.map((line, index) => <div key={`${line.original}-${index}`} className="rounded-xl border p-3"><div className="mb-2 flex items-center gap-2 text-xs opacity-60"><FileText className="size-3.5" /><span>第 {index + 1} 行 · {line.location}</span></div><Input value={line.text} placeholder={line.original} onChange={(event) => setLines((current) => current.map((item, itemIndex) => itemIndex === index ? { ...item, text: event.target.value } : item))} /></div>)}</div> : <div className="flex flex-1 items-center justify-center text-sm opacity-55">没有识别到可编辑文字</div>}
|
||||
<div className="mt-auto flex justify-end gap-2 border-t pt-3"><Button icon={<X className="size-4" />} onClick={onClose}>取消</Button><Button type="primary" icon={<WandSparkles className="size-4" />} disabled={!changed || loading} onClick={() => onConfirm({ lines })}>生成修改结果</Button></div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user