File size: 6,351 Bytes
fe60278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# storyverse_weaver/core/story_engine.py
from PIL import Image # Make sure Pillow is in requirements.txt

class Scene:
    def __init__(self, scene_number: int, user_prompt: str, narrative_text: str = None, 
                 image: Image.Image = None, image_style_prompt: str = None, 
                 image_provider: str = None, error_message: str = None): # Added error_message here
        self.scene_number = scene_number
        self.user_prompt = user_prompt 
        self.narrative_text = narrative_text 
        self.image_style_prompt = image_style_prompt 
        self.image = image 
        self.image_provider = image_provider 
        self.error_message = error_message # Store errors specific to this scene

    def __str__(self):
        img_status = "Yes" if self.image else ("Error" if self.error_message and not self.image else "No")
        narr_status = str(self.narrative_text)[:50] if self.narrative_text and "Error" not in self.narrative_text else ("Error" if "Error" in str(self.narrative_text) else "N/A")
        return f"Scene {self.scene_number}: {self.user_prompt[:50]}... Narrative: {narr_status}... Image: {img_status}"

class Story:
    def __init__(self, title: str = "Untitled StoryVerse"):
        self.title = title
        self.scenes: list[Scene] = []
        self.current_scene_number = 0
        self.global_style_prompt: str = None 
        self.global_negative_prompt: str = None 

    def add_scene_from_elements(self, user_prompt: str, narrative_text: str, 
                                image: Image.Image, image_style_prompt: str, 
                                image_provider: str, error_message: str = None) -> Scene: # Added error_message
        self.current_scene_number += 1
        scene = Scene(
            scene_number=self.current_scene_number,
            user_prompt=user_prompt,
            narrative_text=narrative_text,
            image=image,
            image_style_prompt=image_style_prompt,
            image_provider=image_provider,
            error_message=error_message # Store any error that occurred during this scene's generation
        )
        self.scenes.append(scene)
        print(f"DEBUG: story_engine.py - Added scene {self.current_scene_number}: {scene.user_prompt[:30]}")
        return scene
    
    # This method might be redundant if add_scene_from_elements can handle errors
    # def add_scene_with_error(self, user_prompt: str, error_message: str) -> Scene:
    #     self.current_scene_number += 1
    #     scene = Scene(scene_number=self.current_scene_number, user_prompt=user_prompt, error_message=error_message)
    #     self.scenes.append(scene)
    #     print(f"DEBUG: story_engine.py - Added scene {self.current_scene_number} WITH ERROR: {error_message}")
    #     return scene

    def get_last_scene_narrative(self) -> str:
        if self.scenes and self.scenes[-1].narrative_text and "Error" not in self.scenes[-1].narrative_text:
            return self.scenes[-1].narrative_text
        return "" # Return empty if last scene had error or no narrative

    def get_all_scenes_for_gallery_display(self) -> list[tuple[Image.Image, str]]:
        """Prepares scenes for Gradio Gallery: list of (image, caption)"""
        gallery_items = []
        if not self.scenes:
            # Return a placeholder if no scenes, so gallery doesn't error out
            placeholder_img = Image.new('RGB', (100,100), color='lightgrey')
            gallery_items.append((placeholder_img, "No scenes yet. Weave your story!"))
            return gallery_items

        for scene in self.scenes:
            caption = f"S{scene.scene_number}: {scene.user_prompt[:40]}..."
            if scene.image_style_prompt: caption += f"\nStyle: {scene.image_style_prompt}"
            
            if scene.error_message:
                # Create a placeholder image for errors
                # error_img = Image.new('RGB', (100,100), color='#FFCCCB') # Light red for error
                # For gallery, might be better to use None for image if error occurred, or a default "error" image
                # Gradio gallery might handle None gracefully, or you might need a placeholder PIL image.
                # Let's try None and see how Gradio handles it. If it errors, use a placeholder image.
                gallery_items.append((None, f"{caption}\n⚠️ Error: {scene.error_message[:100]}..."))
            elif scene.image:
                gallery_items.append((scene.image, caption))
            else: # No image, but no specific error reported for the image part of this scene
                placeholder_img = Image.new('RGB', (100,100), color='whitesmoke')
                gallery_items.append((placeholder_img, f"{caption}\n(No image generated for this scene)"))
        return gallery_items

    def get_latest_scene_details_for_display(self) -> tuple[Image.Image, str]:
        """Returns (PIL.Image or None, markdown_string_for_narrative) for the latest scene."""
        if not self.scenes:
            return None, "No scenes yet. Describe your first scene idea above!"

        ls = self.scenes[-1]
        latest_image = ls.image if ls.image else None # Will be None if error or no image
        
        narrative_display = f"## Scene {ls.scene_number}: {ls.user_prompt}\n\n"
        if ls.error_message:
            narrative_display += f"<p style='color:red;'><strong>Generation Error for this scene:</strong><br>{ls.error_message}</p>\n"
        
        if ls.image and not ls.error_message : # Only show style if image was successful
             narrative_display += f"**Style:** {ls.image_style_prompt}\n\n"
        
        if ls.narrative_text:
            if "Error" in ls.narrative_text: # Check if narrative itself is an error message
                narrative_display += f"<p style='color:orange;'>{ls.narrative_text}</p>"
            else:
                narrative_display += ls.narrative_text
        elif not ls.error_message: # No narrative but no major scene error
            narrative_display += "(No narrative generated for this scene)."
            
        return latest_image, narrative_display


    def clear_story(self):
        self.scenes = []
        self.current_scene_number = 0
        print("DEBUG: story_engine.py - Story cleared.")

print("DEBUG: core.story_engine (for StoryVerseWeaver) - Module defined.")