fix: enable text edit after ai copy suggestion
This commit is contained in:
parent
5461cff1db
commit
c00b9e333d
@ -70,9 +70,14 @@ function uniqueStrings(values: string[]) {
|
||||
return Array.from(new Set(values.map((item) => normalizeText(item, 240)).filter(Boolean)));
|
||||
}
|
||||
|
||||
function sanitizeSuggestions(items: unknown[], originalText: string): SuggestionItem[] {
|
||||
function sanitizeSuggestions(
|
||||
items: unknown[],
|
||||
originalText: string,
|
||||
currentText: string
|
||||
): SuggestionItem[] {
|
||||
const seen = new Set<string>();
|
||||
const normalizedOriginal = normalizeText(originalText, 240);
|
||||
const normalizedCurrent = normalizeText(currentText, 240);
|
||||
const suggestions: SuggestionItem[] = [];
|
||||
|
||||
for (const entry of items) {
|
||||
@ -81,7 +86,12 @@ function sanitizeSuggestions(items: unknown[], originalText: string): Suggestion
|
||||
? (entry as Record<string, unknown>)
|
||||
: null;
|
||||
const text = normalizeText(raw?.text as string, 120);
|
||||
if (!text || seen.has(text)) {
|
||||
if (
|
||||
!text ||
|
||||
text === normalizedOriginal ||
|
||||
text === normalizedCurrent ||
|
||||
seen.has(text)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
seen.add(text);
|
||||
@ -94,10 +104,6 @@ function sanitizeSuggestions(items: unknown[], originalText: string): Suggestion
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.length === 0 && normalizedOriginal) {
|
||||
return [{ text: normalizedOriginal, reason: "原文保留" }];
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
@ -131,8 +137,9 @@ function buildSuggestionPrompt(params: {
|
||||
"4. 风格要让用户向往和代入,少一些直接叫卖式表达,少用专业术语,多用大白话,多写体感和生活感。",
|
||||
"5. 要结合给定的人群、场景、核心需求和整页语境,但不要脱离原文所在层级。",
|
||||
"6. 如果原文看起来更像品牌名、型号、编号、价格或固定信息,就不要强行写成夸张广告词,应给更稳妥、接近原意的版本。",
|
||||
"7. 不要输出解释性长文,不要加引号、编号、emoji。",
|
||||
"8. 只返回 JSON,格式必须是:{\"suggestions\":[{\"text\":\"...\",\"reason\":\"...\"}]}。",
|
||||
"7. 不要返回与待改原文或当前输入框内容完全相同的文案,必须给出可触发替换的不同版本。",
|
||||
"8. 不要输出解释性长文,不要加引号、编号、emoji。",
|
||||
"9. 只返回 JSON,格式必须是:{\"suggestions\":[{\"text\":\"...\",\"reason\":\"...\"}]}。",
|
||||
"",
|
||||
`当前模块:${params.presetName || "详情替换结果图改字"}`,
|
||||
`图片区域标题:${params.title || "未命名结果图"}`,
|
||||
@ -206,7 +213,11 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
const parsedJson = parseJsonText(result.text || "");
|
||||
const suggestions = sanitizeSuggestions(parsedJson?.suggestions || [], originalText);
|
||||
const suggestions = sanitizeSuggestions(
|
||||
parsedJson?.suggestions || [],
|
||||
originalText,
|
||||
currentText
|
||||
);
|
||||
|
||||
if (suggestions.length === 0) {
|
||||
return NextResponse.json(
|
||||
|
||||
@ -55,6 +55,42 @@ interface SuggestionStateItem {
|
||||
model?: string | null;
|
||||
}
|
||||
|
||||
function buildTextEditRequestItems(
|
||||
textEditItems: AgentImageTextEditItem[],
|
||||
deletedTextItems: DeletedTextItem[]
|
||||
) {
|
||||
const changedItems: CanvasTextEditPayloadItem[] = textEditItems
|
||||
.map((item) => {
|
||||
const editedText = item.editedText.trim();
|
||||
const action: "replace" | "delete" =
|
||||
editedText.length === 0 ? "delete" : "replace";
|
||||
return {
|
||||
id: item.id,
|
||||
originalText: item.originalText.trim(),
|
||||
editedText,
|
||||
action,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(item) =>
|
||||
item.originalText.length > 0 &&
|
||||
(item.action === "delete" ||
|
||||
(item.editedText &&
|
||||
item.editedText.length > 0 &&
|
||||
item.originalText !== item.editedText))
|
||||
);
|
||||
|
||||
const removedItems: CanvasTextEditPayloadItem[] = deletedTextItems
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
originalText: item.originalText.trim(),
|
||||
action: "delete" as const,
|
||||
}))
|
||||
.filter((item) => item.originalText.length > 0);
|
||||
|
||||
return [...changedItems, ...removedItems];
|
||||
}
|
||||
|
||||
function isHttpImageUrl(value?: string | null) {
|
||||
if (!value) return false;
|
||||
try {
|
||||
@ -204,19 +240,11 @@ export function AgentImageTextEditDialog({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const hasTextEditChanges = useMemo(
|
||||
() =>
|
||||
textEditItems.some((item) => {
|
||||
const originalText = item.originalText.trim();
|
||||
const editedText = item.editedText.trim();
|
||||
return (
|
||||
originalText.length > 0 &&
|
||||
(editedText.length === 0 || editedText !== originalText)
|
||||
);
|
||||
}) ||
|
||||
deletedTextItems.some((item) => item.originalText.trim().length > 0),
|
||||
const textEditRequestItems = useMemo(
|
||||
() => buildTextEditRequestItems(textEditItems, deletedTextItems),
|
||||
[deletedTextItems, textEditItems]
|
||||
);
|
||||
const hasTextEditChanges = textEditRequestItems.length > 0;
|
||||
|
||||
const handleGenerateSuggestions = useCallback(
|
||||
async (item: AgentImageTextEditItem) => {
|
||||
@ -257,7 +285,18 @@ export function AgentImageTextEditDialog({
|
||||
presetName: copySuggestionContext?.presetName || undefined,
|
||||
});
|
||||
|
||||
if (!result.success || !result.data?.suggestions?.length) {
|
||||
const currentItem = textEditItems.find((entry) => entry.id === item.id);
|
||||
const blockedTexts = new Set(
|
||||
[item.originalText, currentItem?.editedText]
|
||||
.map((entry) => String(entry || "").trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
const suggestions = (result.data?.suggestions || []).filter((suggestion) => {
|
||||
const text = String(suggestion.text || "").trim();
|
||||
return Boolean(text) && !blockedTexts.has(text);
|
||||
});
|
||||
|
||||
if (!result.success || suggestions.length === 0) {
|
||||
throw new Error(result.error || "未生成可用文案建议");
|
||||
}
|
||||
|
||||
@ -265,7 +304,7 @@ export function AgentImageTextEditDialog({
|
||||
...current,
|
||||
[item.id]: {
|
||||
isLoading: false,
|
||||
suggestions: result.data?.suggestions || [],
|
||||
suggestions,
|
||||
error: null,
|
||||
model: result.data?.model || null,
|
||||
},
|
||||
@ -289,9 +328,24 @@ export function AgentImageTextEditDialog({
|
||||
);
|
||||
|
||||
const handleApplySuggestion = useCallback((id: string, value: string) => {
|
||||
handleTextItemChange(id, value);
|
||||
toast.success("已套用推荐文案");
|
||||
}, [handleTextItemChange]);
|
||||
const nextText = String(value || "").trim();
|
||||
const target = textEditItems.find((item) => item.id === id);
|
||||
if (!nextText) {
|
||||
toast.info("这条推荐文案为空,请换一条");
|
||||
return;
|
||||
}
|
||||
if (target && nextText === target.originalText.trim()) {
|
||||
toast.info("这条和原文一样,不会触发生成,请换一条推荐文案");
|
||||
return;
|
||||
}
|
||||
if (target && nextText === target.editedText.trim()) {
|
||||
toast.info("这条已经在输入框里了");
|
||||
return;
|
||||
}
|
||||
|
||||
handleTextItemChange(id, nextText);
|
||||
toast.success("已套用推荐文案,可以点击确定生图");
|
||||
}, [handleTextItemChange, textEditItems]);
|
||||
|
||||
const handleApplyTextEdit = useCallback(async () => {
|
||||
if (!normalizedImageUrl) {
|
||||
@ -308,36 +362,7 @@ export function AgentImageTextEditDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const changedItems: CanvasTextEditPayloadItem[] = textEditItems
|
||||
.map((item) => {
|
||||
const editedText = item.editedText.trim();
|
||||
const action: "replace" | "delete" = editedText.length === 0 ? "delete" : "replace";
|
||||
return {
|
||||
id: item.id,
|
||||
originalText: item.originalText.trim(),
|
||||
editedText,
|
||||
action,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(item) =>
|
||||
item.originalText.length > 0 &&
|
||||
(item.action === "delete" ||
|
||||
(item.editedText &&
|
||||
item.editedText.length > 0 &&
|
||||
item.originalText !== item.editedText))
|
||||
);
|
||||
|
||||
const removedItems: CanvasTextEditPayloadItem[] = deletedTextItems
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
originalText: item.originalText.trim(),
|
||||
action: "delete" as const,
|
||||
}))
|
||||
.filter((item) => item.originalText.length > 0);
|
||||
|
||||
const requestItems = [...changedItems, ...removedItems];
|
||||
if (requestItems.length === 0) {
|
||||
if (textEditRequestItems.length === 0) {
|
||||
toast.info("请先把要替换的文字改成新文案,或点击垃圾桶删除该文字");
|
||||
return;
|
||||
}
|
||||
@ -348,7 +373,7 @@ export function AgentImageTextEditDialog({
|
||||
try {
|
||||
const result = await imageGenerationService.editCanvasText({
|
||||
imageUrl: normalizedImageUrl,
|
||||
items: requestItems,
|
||||
items: textEditRequestItems,
|
||||
});
|
||||
|
||||
if (!result.success || !result.data?.originalUrl) {
|
||||
@ -372,12 +397,11 @@ export function AgentImageTextEditDialog({
|
||||
setIsApplyingTextEdit(false);
|
||||
}
|
||||
}, [
|
||||
deletedTextItems,
|
||||
isApplyingTextEdit,
|
||||
normalizedImageUrl,
|
||||
onApplySuccess,
|
||||
onOpenChange,
|
||||
textEditItems,
|
||||
textEditRequestItems,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user