feat(ai-character): 수정 요청 변경 필드만 전송
This commit is contained in:
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { navigateTo } from "@/app/browser-location";
|
||||
import { routePaths } from "@/app/route-paths";
|
||||
import { getCharacter, updateCharacter } from "@/features/characters/api/character-api";
|
||||
import type { UpdateCharacterParams } from "@/features/characters/api/character-api";
|
||||
import { CharacterOptionalFields } from "@/features/characters/components/CharacterOptionalFields";
|
||||
import { characterOptionalFieldsFromDetail, toUpdateCharacterOptionalRequest } from "@/features/characters/components/character-optional-field-serialization";
|
||||
import { OriginalWorkSearchField } from "@/features/characters/components/OriginalWorkSearchField";
|
||||
@@ -64,6 +65,10 @@ function validateImage(image: File | null): string | undefined {
|
||||
return "JPEG 또는 PNG 파일만 업로드하세요.";
|
||||
}
|
||||
|
||||
function hasRequestFields(request: UpdateCharacterParams["request"]): boolean {
|
||||
return Object.keys(request).length > 0;
|
||||
}
|
||||
|
||||
function CharacterEditForm({ apiClient, character, createCropSource, renderCrop }: { readonly apiClient: ApiClient; readonly character: CharacterDetail; readonly createCropSource: (file: File) => Promise<CropSourceImage>; readonly renderCrop?: (request: CropRenderRequest) => Promise<File> }) {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [description, setDescription] = useState(character.description);
|
||||
@@ -78,7 +83,19 @@ function CharacterEditForm({ apiClient, character, createCropSource, renderCrop
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const imageSelectionId = useRef(0);
|
||||
const detailPath = routePaths.aiCharacterDetail(String(character.id));
|
||||
const dirty = image !== null || isImagePreparing || cropSource !== null || description !== character.description || name !== character.name || originalWork?.id !== character.originalWork?.id || systemPrompt !== character.systemPrompt || JSON.stringify(optionalFields) !== JSON.stringify(characterOptionalFieldsFromDetail(character));
|
||||
const initialOptionalFields = characterOptionalFieldsFromDetail(character);
|
||||
const initialDescription = character.description.trim();
|
||||
const initialName = character.name.trim();
|
||||
const initialSystemPrompt = character.systemPrompt.trim();
|
||||
const request: UpdateCharacterParams["request"] = {
|
||||
...toUpdateCharacterOptionalRequest(optionalFields, initialOptionalFields),
|
||||
...(description.trim() === initialDescription ? {} : { description: description.trim() }),
|
||||
...(name.trim() === initialName ? {} : { name: name.trim() }),
|
||||
...(originalWork !== null && originalWork.id !== character.originalWork?.id ? { originalWorkId: originalWork.id } : {}),
|
||||
...(systemPrompt.trim() === initialSystemPrompt ? {} : { systemPrompt: systemPrompt.trim() }),
|
||||
};
|
||||
const hasChanges = image !== null || hasRequestFields(request);
|
||||
const dirty = hasChanges || isImagePreparing || cropSource !== null;
|
||||
const imageSubmitBlocked = isImagePreparing || cropSource !== null;
|
||||
|
||||
useEffect(() => () => cropSource?.release?.(), [cropSource]);
|
||||
@@ -143,6 +160,9 @@ function CharacterEditForm({ apiClient, character, createCropSource, renderCrop
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextErrors = validate();
|
||||
setErrors(nextErrors);
|
||||
@@ -155,13 +175,7 @@ function CharacterEditForm({ apiClient, character, createCropSource, renderCrop
|
||||
try {
|
||||
await updateCharacter(apiClient, String(character.id), {
|
||||
image: image ?? undefined,
|
||||
request: {
|
||||
...toUpdateCharacterOptionalRequest(optionalFields),
|
||||
description: description.trim(),
|
||||
name: name.trim(),
|
||||
originalWorkId: originalWork?.id,
|
||||
systemPrompt: systemPrompt.trim(),
|
||||
},
|
||||
request,
|
||||
});
|
||||
navigateTo(routePaths.aiCharacterDetail(String(character.id)), { successNotification: "AI 캐릭터를 저장했습니다." });
|
||||
} catch (error: unknown) {
|
||||
@@ -208,7 +222,7 @@ function CharacterEditForm({ apiClient, character, createCropSource, renderCrop
|
||||
<button className="rounded-md border border-input bg-card px-4 py-2 font-semibold hover:bg-accent" onClick={(event) => requestRouteLeave(event.currentTarget, () => navigateTo(detailPath))} type="button">
|
||||
상세로 돌아가기
|
||||
</button>
|
||||
<button className="rounded-md border border-input bg-primary px-4 py-2 font-semibold text-primary-foreground hover:bg-[var(--button-bg-hover)] active:bg-[var(--button-bg-active)] disabled:opacity-60" disabled={isSubmitting || imageSubmitBlocked} type="submit">
|
||||
<button className="rounded-md border border-input bg-primary px-4 py-2 font-semibold text-primary-foreground hover:bg-[var(--button-bg-hover)] active:bg-[var(--button-bg-active)] disabled:opacity-60" disabled={isSubmitting || imageSubmitBlocked || !hasChanges} type="submit">
|
||||
{isSubmitting ? "저장 중" : "저장"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user