Skip to content

Commit 8cd6a34

Browse files
committed
add character status logic and enhance panel error handling
1 parent b4c7b70 commit 8cd6a34

File tree

2 files changed

+173
-49
lines changed

2 files changed

+173
-49
lines changed

src/app/page.tsx

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,17 @@ export default function Home() {
194194
return { isCompleted: false, isInProgress: true };
195195
};
196196

197+
// Helper function for character status logic
198+
const getCharacterStatus = () => {
199+
const expectedCount = storyAnalysis?.characters.length || 0;
200+
const currentCount = characterReferences.length;
201+
202+
if (currentCount === 0) return { isCompleted: false, isInProgress: false };
203+
if (currentCount === expectedCount && expectedCount > 0)
204+
return { isCompleted: true, isInProgress: false };
205+
return { isCompleted: false, isInProgress: true };
206+
};
207+
197208
const wordCount = story
198209
.trim()
199210
.split(/\s+/)
@@ -925,43 +936,93 @@ export default function Home() {
925936
id={charactersHeadingId}
926937
title="Character Designs"
927938
stepNumber={2}
928-
isCompleted={characterReferences.length > 0}
939+
isCompleted={getCharacterStatus().isCompleted}
929940
isInProgress={
930-
isGenerating &&
931-
!!storyAnalysis &&
932-
characterReferences.length === 0 &&
933-
currentStepText.includes("character")
941+
getCharacterStatus().isInProgress ||
942+
(isGenerating &&
943+
!!storyAnalysis &&
944+
currentStepText.includes("character"))
934945
}
935946
isOpen={openAccordions.has("characters")}
936947
onToggle={() => toggleAccordionSection("characters")}
937948
showStatus={isGenerating || characterReferences.length > 0}
938949
>
939-
{characterReferences.length > 0 ? (
940-
<div className="character-grid">
950+
{storyAnalysis ? (
951+
<div>
941952
<div className="flex justify-between items-center mb-3">
942953
<h5 className="font-semibold">Character Designs</h5>
943-
<DownloadButton
944-
onClick={() => downloadCharacters(characterReferences)}
945-
isLoading={isDownloadingCharacters}
946-
label="Download All Characters"
947-
loadingText="Creating zip..."
948-
variant="outline"
949-
/>
950-
</div>
951-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
952-
{characterReferences.map((char) => (
953-
<CharacterCard
954-
key={char.name}
955-
character={char}
956-
showImage={true}
957-
onImageClick={openImageModal}
958-
onDownload={() => downloadCharacter(char)}
959-
onRegenerate={() =>
960-
handleRegenerateCharacter(char.name)
954+
{characterReferences.length > 0 && (
955+
<DownloadButton
956+
onClick={() =>
957+
downloadCharacters(characterReferences)
961958
}
962-
isRegenerating={regeneratingCharacters.has(char.name)}
959+
isLoading={isDownloadingCharacters}
960+
label="Download All Characters"
961+
loadingText="Creating zip..."
962+
variant="outline"
963963
/>
964-
))}
964+
)}
965+
</div>
966+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
967+
{storyAnalysis.characters.map((expectedChar) => {
968+
const generatedChar = characterReferences.find(
969+
(c) => c.name === expectedChar.name,
970+
);
971+
const isCurrentlyGenerating =
972+
isGenerating &&
973+
currentStepText.includes("character") &&
974+
currentStepText.includes(expectedChar.name);
975+
976+
if (generatedChar) {
977+
// Show completed character with image
978+
return (
979+
<CharacterCard
980+
key={expectedChar.name}
981+
character={generatedChar}
982+
showImage={true}
983+
onImageClick={openImageModal}
984+
onDownload={() =>
985+
downloadCharacter(generatedChar)
986+
}
987+
onRegenerate={() =>
988+
handleRegenerateCharacter(generatedChar.name)
989+
}
990+
isRegenerating={regeneratingCharacters.has(
991+
generatedChar.name,
992+
)}
993+
/>
994+
);
995+
} else {
996+
// Show placeholder for pending/generating character
997+
return (
998+
<div
999+
key={`placeholder-${expectedChar.name}`}
1000+
className={`card-manga ${isCurrentlyGenerating ? "animate-pulse" : ""} border-dashed border-2 border-manga-medium-gray/50 bg-manga-medium-gray/10`}
1001+
>
1002+
<div className="card-body text-center py-8">
1003+
{isCurrentlyGenerating ? (
1004+
<>
1005+
<div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-manga-black mb-2"></div>
1006+
<h6 className="card-title">
1007+
Generating {expectedChar.name}...
1008+
</h6>
1009+
</>
1010+
) : (
1011+
<h6 className="card-title text-manga-medium-gray">
1012+
{expectedChar.name}
1013+
</h6>
1014+
)}
1015+
<p className="card-text text-sm text-manga-medium-gray/80 mt-2">
1016+
{expectedChar.physicalDescription}
1017+
</p>
1018+
<p className="card-text text-xs text-manga-medium-gray/60">
1019+
<em>{expectedChar.role}</em>
1020+
</p>
1021+
</div>
1022+
</div>
1023+
);
1024+
}
1025+
})}
9651026
</div>
9661027
<div className="mt-3">
9671028
<RerunButton

src/stores/useGenerationStore.ts

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import type {
1818
} from "@/types";
1919

2020
type FailedStep = "analysis" | "characters" | "layout" | "panels" | null;
21-
type FailedPanel = { step: "panel"; panelNumber: number } | null;
21+
type FailedPanel = {
22+
step: "panel";
23+
panelNumber: number;
24+
panelIndex: number;
25+
} | null;
2226

2327
// IndexedDB setup for images
2428
const DB_NAME = "MangaGeneratorDB";
@@ -889,7 +893,13 @@ export const useGenerationStore = create<GenerationState & GenerationActions>()(
889893
"panel_generation_failed",
890894
`Panel ${i + 1}: ${errorMessage}`,
891895
);
892-
set({ failedPanel: { step: "panel", panelNumber: i + 1 } });
896+
set({
897+
failedPanel: {
898+
step: "panel",
899+
panelNumber: i + 1,
900+
panelIndex: i,
901+
},
902+
});
893903
throw error;
894904
}
895905
}
@@ -906,10 +916,19 @@ export const useGenerationStore = create<GenerationState & GenerationActions>()(
906916
const errorMessage =
907917
error instanceof Error ? error.message : "Generation failed";
908918

919+
const state = _get();
909920
_get().setErrorWithContext(
910921
errorMessage,
911922
currentStep ? `${currentStep} step failed` : "Generation",
912-
currentStep ? () => _get().retryFromStep(currentStep) : undefined,
923+
currentStep === "panels" && state.failedPanel
924+
? () =>
925+
_get().retryFailedPanel(
926+
state.failedPanel!.panelNumber,
927+
state.failedPanel!.panelIndex,
928+
)
929+
: currentStep
930+
? () => _get().retryFromStep(currentStep)
931+
: undefined,
913932
);
914933
set({
915934
isGenerating: false,
@@ -931,21 +950,56 @@ export const useGenerationStore = create<GenerationState & GenerationActions>()(
931950
return;
932951
}
933952

953+
// Set appropriate loading state based on step
954+
const uiStore = useUIStore.getState();
955+
switch (step) {
956+
case "analysis":
957+
uiStore.setIsRerunningAnalysis(true);
958+
break;
959+
case "characters":
960+
uiStore.setIsRerunningCharacters(true);
961+
break;
962+
case "layout":
963+
uiStore.setIsRerunningLayout(true);
964+
break;
965+
case "panels":
966+
uiStore.setIsRerunningPanels(true);
967+
break;
968+
}
969+
934970
trackEvent({
935971
action: "retry_from_step",
936972
category: "user_interaction",
937973
label: step,
938974
});
939975

940-
// Use the main generation flow starting from the specified step
941-
await _get().generateComic(
942-
state.originalStoryText,
943-
state.originalStyle,
944-
state.originalNoDialogue,
945-
state.originalUploadedCharacterReferences || [],
946-
state.originalUploadedSettingReferences || [],
947-
step,
948-
);
976+
try {
977+
// Use the main generation flow starting from the specified step
978+
await _get().generateComic(
979+
state.originalStoryText,
980+
state.originalStyle,
981+
state.originalNoDialogue,
982+
state.originalUploadedCharacterReferences || [],
983+
state.originalUploadedSettingReferences || [],
984+
step,
985+
);
986+
} finally {
987+
// Clear loading state regardless of success or failure
988+
switch (step) {
989+
case "analysis":
990+
uiStore.setIsRerunningAnalysis(false);
991+
break;
992+
case "characters":
993+
uiStore.setIsRerunningCharacters(false);
994+
break;
995+
case "layout":
996+
uiStore.setIsRerunningLayout(false);
997+
break;
998+
case "panels":
999+
uiStore.setIsRerunningPanels(false);
1000+
break;
1001+
}
1002+
}
9491003
},
9501004

9511005
retryFailedPanel: async (panelNumber, panelIndex) => {
@@ -958,22 +1012,31 @@ export const useGenerationStore = create<GenerationState & GenerationActions>()(
9581012
return;
9591013
}
9601014

1015+
// Set loading state for panels
1016+
const uiStore = useUIStore.getState();
1017+
uiStore.setIsRerunningPanels(true);
1018+
9611019
trackEvent({
9621020
action: "retry_failed_panel",
9631021
category: "user_interaction",
9641022
label: `panel_${panelNumber}`,
9651023
});
9661024

967-
// Use the main generation flow starting from the failed panel
968-
await _get().generateComic(
969-
state.originalStoryText,
970-
state.originalStyle,
971-
state.originalNoDialogue,
972-
state.originalUploadedCharacterReferences || [],
973-
state.originalUploadedSettingReferences || [],
974-
"panels",
975-
panelIndex,
976-
);
1025+
try {
1026+
// Use the main generation flow starting from the failed panel
1027+
await _get().generateComic(
1028+
state.originalStoryText,
1029+
state.originalStyle,
1030+
state.originalNoDialogue,
1031+
state.originalUploadedCharacterReferences || [],
1032+
state.originalUploadedSettingReferences || [],
1033+
"panels",
1034+
panelIndex,
1035+
);
1036+
} finally {
1037+
// Clear loading state regardless of success or failure
1038+
uiStore.setIsRerunningPanels(false);
1039+
}
9771040
},
9781041

9791042
regenerateCharacter: async (characterName) => {

0 commit comments

Comments
 (0)