Shiva4113 commited on
Commit
a7f3368
·
1 Parent(s): 29e8f93

Initial Commit

Browse files
Files changed (11) hide show
  1. .gitignore +15 -0
  2. .gradio/certificate.pem +31 -0
  3. README.md +139 -12
  4. app.py +541 -0
  5. data/stories/example_story.txt +11 -0
  6. data/templates/template.png +0 -0
  7. manga.py +616 -0
  8. pyproject.toml +13 -0
  9. requirements.txt +5 -0
  10. utils.py +238 -0
  11. uv.lock +0 -0
.gitignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ **/.DS_Store
3
+ .venv
4
+
5
+ # Python-generated files
6
+ *.py[oc]
7
+ build/
8
+ dist/
9
+ wheels/
10
+ *.egg-info
11
+
12
+ # Virtual environments
13
+ .venv
14
+
15
+ data/examples/*/scene*.png
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
README.md CHANGED
@@ -1,12 +1,139 @@
1
- ---
2
- title: MangakAI
3
- emoji: 🐢
4
- colorFrom: yellow
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.44.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MangakAI 📚✨
2
+
3
+ Transform your stories into stunning manga panels with AI! MangakAI is an intelligent manga generation tool that converts written narratives into visual manga-style panels using Google's Gemini AI models.
4
+
5
+ ## 🌟 Features
6
+
7
+ - **🎨 AI-Powered Generation**: Convert stories into professional manga panels with scene intelligence
8
+ - **🎭 Style Customization**: Multiple art styles, moods, color palettes, and composition options
9
+ - **🔄 Panel Management**: Regenerate specific panels with custom modifications and reference images
10
+ - **📋 Custom Templates**: Upload your own panel layouts for personalized manga creation
11
+ - **📁 Export Options**: Generate PDFs and organize panels with version control
12
+ - **🖥️ Web Interface**: User-friendly Gradio interface with text/file input and example stories
13
+
14
+ ## 🚀 Quick Start
15
+
16
+ ### Prerequisites
17
+ - Python 3.11 or higher
18
+ - Google Gemini API key
19
+ - UV package manager (recommended) or pip
20
+
21
+ ### Installation
22
+
23
+ 1. **Clone the repository**
24
+ ```bash
25
+ git clone https://github.com/Shiva4113/MangakAI.git
26
+ cd MangakAI
27
+ ```
28
+
29
+ 2. **Install dependencies**
30
+ ```bash
31
+ # Using UV (recommended)
32
+ uv sync
33
+ ```
34
+
35
+ 3. **Set up environment variables**
36
+ ```bash
37
+ # Create .env file
38
+ echo "GEMINI_API_KEY=your_gemini_api_key_here" > .env
39
+ ```
40
+
41
+ 4. **Run the application**
42
+ ```bash
43
+ uv run app.py
44
+ ```
45
+
46
+ 5. **Access the interface**
47
+ Open your browser and navigate to `http://localhost:7860`
48
+
49
+ ## 📖 Usage Guide
50
+
51
+ ### 🖋️ **Generate Manga from Text**
52
+
53
+ 1. Enter your story in the text area
54
+ 2. Select number of scenes (1-10 panels)
55
+ 3. Choose style preferences (optional)
56
+ 4. Upload custom template (optional)
57
+ 5. Click "Generate Manga"
58
+
59
+ ### 📄 **Generate from File**
60
+
61
+ 1. Upload a .txt story file
62
+ 2. Configure settings and generate
63
+
64
+ ### 🔄 **Regenerate Panels**
65
+
66
+ 1. Select panel number to modify
67
+ 2. Enter modification instructions
68
+ 3. Upload reference image (optional)
69
+ 4. Choose to replace original or keep both
70
+
71
+ ### 📥 **Export as PDF**
72
+
73
+ 1. Generate your manga first
74
+ 2. Go to "Download PDF" tab and click "Create PDF"
75
+
76
+ ## 🏗️ Project Structure
77
+
78
+ ```
79
+ MangakAI/
80
+ ├── app.py # Main Gradio interface
81
+ ├── manga.py # Core manga generation logic
82
+ ├── utils.py # Utility functions and prompts
83
+ ├── main.py # Entry point
84
+ ├── pyproject.toml # Project configuration
85
+ ├── .env # Environment variables (create this)
86
+ ├── data/
87
+ │ ├── examples/ # Example manga panels
88
+ │ │ ├── LittleLantern/
89
+ │ │ ├── PaperKite/
90
+ │ │ └── StrayPuppy/
91
+ │ ├── output/ # Generated manga panels
92
+ │ ├── stories/ # Story text files
93
+ │ └── templates/ # Panel templates
94
+ └── README.md
95
+ ```
96
+
97
+ ## 🛠️ Configuration
98
+
99
+ ### Environment Variables
100
+
101
+ Create a `.env` file with the following variables:
102
+
103
+ ```env
104
+ GEMINI_API_KEY=your_gemini_api_key_here
105
+ TEMPLATE_PATH=data/templates/template1.png
106
+ OUTPUT_DIR=data/output
107
+ STORIES_DIR=data/stories
108
+ IMAGE_MODEL_NAME=gemini-2.5-flash-image-preview
109
+ SCENE_MODEL_NAME=gemini-2.0-flash
110
+ ```
111
+
112
+ ### API Setup
113
+
114
+ 1. **Get Gemini API Key**:
115
+ - Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
116
+ - Create a new API key
117
+ - Add it to your `.env` file
118
+
119
+ ## 🎨 Style Options
120
+
121
+ Choose from various art styles (Traditional Manga, Shonen, Shoujo, Seinen, Chibi, Cyberpunk, Fantasy, Horror), moods (Epic, Dark, Light, Dramatic, Action-packed), color palettes, character styles, and composition options to create your unique manga aesthetic.
122
+
123
+ ## 🔧 Advanced Features
124
+
125
+ - **Smart Prompts**: Analyzes story structure and maintains character consistency
126
+ - **Custom Templates**: Upload your own panel layouts with automatic AI adaptation
127
+ - **Reference Images**: Guide style, composition, and character appearance
128
+
129
+ ## 📋 Examples
130
+
131
+ The project includes three example stories with generated panels:
132
+
133
+ 1. **The Little Lantern**: A heartwarming tale of courage and kindness
134
+ 2. **The Paper Kite**: A story about letting go and finding wonder
135
+ 3. **The Stray Puppy**: A touching story of compassion and friendship
136
+
137
+
138
+
139
+ *Transform your imagination into visual stories with the power of AI!*
app.py ADDED
@@ -0,0 +1,541 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from manga import (
3
+ generate_manga_interface,
4
+ generate_manga_from_file_interface,
5
+ regenerate_and_replace_interface,
6
+ create_pdf_interface,
7
+ get_current_panels,
8
+ create_user_session
9
+ )
10
+ from utils import ART_STYLES, MOOD_OPTIONS, COLOR_PALETTES, CHARACTER_STYLES, LINE_STYLES, COMPOSITION_STYLES
11
+ import os
12
+
13
+ # Define your example data
14
+ EXAMPLES = {
15
+ "The Little Lantern": {
16
+ "title": "The Little Lantern",
17
+ "story": """In a small coastal village, where the sea whispered secrets to the shore, there lived a curious boy named Arun. Every evening, as the sun dipped behind the hills and the sky turned shades of orange and violet, Arun would carry his little lantern and sit on the rocks, watching the restless waves dance under the fading light.
18
+
19
+ His lantern was old, with a glass chimney slightly cracked, but Arun cared for it as if it were a treasure. "One day, this light will help someone," he would whisper to himself, as the sea breeze tousled his hair.
20
+
21
+ One night, the sky darkened earlier than usual. Thick clouds rolled in, and the wind began to howl like a wild beast. The sea churned, waves slamming against the shore with fierce force. Arun, worried, held his lantern tighter and scanned the horizon.
22
+
23
+ Suddenly, through the curtain of rain and spray, he spotted a small fishing boat tossed about by the waves, its lantern barely visible. The boat's crew struggled to control it, fear written on their faces.
24
+
25
+ Without a second thought, Arun jumped onto the rocks, his lantern held high above his head. "Here! This way!" he shouted through the roar of the wind and water.
26
+
27
+ The captain of the boat saw the glow and turned the vessel toward the shore. The villagers, alerted by Arun's brave signal, ran to the beach with ropes and nets. Together, they pulled the boat safely onto land.
28
+
29
+ The captain, drenched but smiling, knelt down and embraced Arun. "You saved us," he said. "Your little lantern became our lighthouse."
30
+
31
+ From that day forward, Arun's lantern became more than just an object—it became a symbol of courage and kindness. Every evening, Arun continued to sit by the sea, his light shining steadily. The villagers often gathered around him, sharing stories, laughter, and warmth. And whenever a storm brewed on the horizon, Arun's lantern stood as a reminder that even the smallest light, held with love, can brighten the darkest night.""",
32
+ "panels": [
33
+ "data/examples/LittleLantern/scene1.png",
34
+ "data/examples/LittleLantern/scene2.png",
35
+ "data/examples/LittleLantern/scene3.png",
36
+ "data/examples/LittleLantern/scene4.png",
37
+ "data/examples/LittleLantern/scene5.png"
38
+ ]
39
+ },
40
+ "The Paper Kite": {
41
+ "title": "The Paper Kite",
42
+ "story": """Mira loved making kites. She would spend hours folding colorful paper, gluing tails, and tying strings until each kite looked perfect. Her favorite one was a bright red kite with golden edges that danced beautifully in the wind.
43
+
44
+ One afternoon, while flying it near the meadow, a sudden gust of wind tore the string and the kite soared away. Mira's heart sank as she watched it drift higher and higher, beyond the trees and far into the sky.
45
+
46
+ Feeling sad, she sat on a rock, staring at the empty sky. Just then, an old man passing by smiled gently. "Sometimes, letting go leads to something wonderful," he said.
47
+
48
+ The next morning, while walking through the meadow, Mira found her kite caught on a branch. But this time, instead of grabbing it, she smiled and left it there. She realized the wind had given her kite a chance to explore new heights.
49
+
50
+ From that day on, Mira made many more kites—not all stayed with her, but each one carried her dreams farther than she ever imagined.""",
51
+ "panels": [
52
+ "data/examples/PaperKite/scene1.png",
53
+ "data/examples/PaperKite/scene2.png",
54
+ "data/examples/PaperKite/scene3.png",
55
+ "data/examples/PaperKite/scene4.png",
56
+ "data/examples/PaperKite/scene5.png"
57
+ ]
58
+ },
59
+ "The Stray Puppy": {
60
+ "title": "The Stray Puppy",
61
+ "story": """One chilly evening, as the sun was setting, Leo was walking home from school when he heard soft whimpering near the park bench. Curious, he peeked behind it and found a tiny, shivering puppy with big, sad eyes.
62
+
63
+ Leo gently picked it up and wrapped it in his jacket. The puppy licked his cheek as if thanking him. He took it home, gave it warm milk, and made a little bed from an old blanket.
64
+
65
+ The next day, Leo asked his neighbors if the puppy belonged to anyone, but no one came forward. So he decided to keep it and named it "Buddy".""",
66
+ "panels": [
67
+ "data/examples/StrayPuppy/scene1.png",
68
+ "data/examples/StrayPuppy/scene2.png",
69
+ "data/examples/StrayPuppy/scene3.png",
70
+ "data/examples/StrayPuppy/scene4.png"
71
+ ]
72
+ }
73
+ }
74
+
75
+ def load_example(example_name):
76
+ """Load example data when user selects an example."""
77
+ if example_name in EXAMPLES:
78
+ example = EXAMPLES[example_name]
79
+ existing_panels = [panel for panel in example["panels"] if os.path.exists(panel)]
80
+ return example["title"], example["story"], existing_panels
81
+ return "", "", []
82
+
83
+ def initialize_session(api_key: str):
84
+ """Initialize a new user session with API key."""
85
+ try:
86
+ if not api_key or not api_key.strip():
87
+ return None, "❌ Please enter your API key", gr.Tabs(visible=False)
88
+
89
+ session_id = create_user_session(api_key.strip())
90
+ status_message = f"✅ Session initialized successfully! Session ID: {session_id[:8]}..."
91
+
92
+ return session_id, status_message, gr.Tabs(visible=True)
93
+ except Exception as e:
94
+ return None, f"❌ Error initializing session: {str(e)}", gr.Tabs(visible=False)
95
+
96
+ def create_gradio_interface():
97
+ with gr.Blocks(title="MangakAI", theme=gr.themes.Soft()) as demo:
98
+ gr.Markdown("# 📚 MangakAI")
99
+ gr.Markdown("Transform your stories into manga panels with AI and custom style preferences!")
100
+
101
+ # Session state
102
+ session_id_state = gr.State(value=None)
103
+
104
+ # API Key Section at the top
105
+ gr.Markdown("## 🔑 API Configuration")
106
+ gr.Markdown("**Enter your Gemini API Key to get started.** You can get one from [Google AI Studio](https://makersuite.google.com/app/apikey)")
107
+
108
+ with gr.Row():
109
+ api_key_input = gr.Textbox(
110
+ label="Gemini API Key",
111
+ placeholder="Enter your Gemini API key here...",
112
+ type="password",
113
+ scale=4
114
+ )
115
+ initialize_btn = gr.Button("🚀 Initialize Session", variant="primary", scale=1)
116
+
117
+ session_status = gr.Textbox(
118
+ label="Session Status",
119
+ value="⚠️ Please enter your API key and initialize session",
120
+ interactive=False
121
+ )
122
+
123
+ gr.Markdown("---")
124
+
125
+ # Main tabs (initially hidden until session is initialized)
126
+ main_tabs = gr.Tabs(visible=False)
127
+
128
+ with main_tabs:
129
+ with gr.Tab("📝 Generate Manga"):
130
+ with gr.Tab("Text Input"):
131
+ with gr.Row():
132
+ with gr.Column(scale=2):
133
+ story_input = gr.Textbox(
134
+ label="Enter your story",
135
+ placeholder="Once upon a time...",
136
+ lines=10
137
+ )
138
+ with gr.Column(scale=1):
139
+ num_scenes_text = gr.Slider(
140
+ minimum=1,
141
+ maximum=10,
142
+ value=5,
143
+ step=1,
144
+ label="Number of Scenes"
145
+ )
146
+
147
+ # Style Preferences Section
148
+ gr.Markdown("### 🎨 Style Preferences")
149
+ with gr.Row():
150
+ with gr.Column():
151
+ art_style_text = gr.Dropdown(
152
+ choices=["None"] + ART_STYLES,
153
+ value="None",
154
+ label="Art Style"
155
+ )
156
+ mood_text = gr.Dropdown(
157
+ choices=["None"] + MOOD_OPTIONS,
158
+ value="None",
159
+ label="Overall Mood"
160
+ )
161
+ color_palette_text = gr.Dropdown(
162
+ choices=["None"] + COLOR_PALETTES,
163
+ value="None",
164
+ label="Color Palette"
165
+ )
166
+ with gr.Column():
167
+ character_style_text = gr.Dropdown(
168
+ choices=["None"] + CHARACTER_STYLES,
169
+ value="None",
170
+ label="Character Style"
171
+ )
172
+ line_style_text = gr.Dropdown(
173
+ choices=["None"] + LINE_STYLES,
174
+ value="None",
175
+ label="Line Art Style"
176
+ )
177
+ composition_text = gr.Dropdown(
178
+ choices=["None"] + COMPOSITION_STYLES,
179
+ value="None",
180
+ label="Composition Style"
181
+ )
182
+
183
+ additional_notes_text = gr.Textbox(
184
+ label="Additional Style Notes",
185
+ placeholder="Any specific style preferences, character descriptions, or artistic directions...",
186
+ lines=3
187
+ )
188
+
189
+ # Custom Template Upload Section
190
+ gr.Markdown("### 📋 Custom Template (Optional)")
191
+ gr.Markdown("*Upload your own manga panel template. If not provided, default template will be used.*")
192
+ with gr.Row():
193
+ user_template_text = gr.File(
194
+ label="Upload Custom Template",
195
+ file_types=[".png", ".jpg", ".jpeg", ".webp"]
196
+ )
197
+
198
+ gr.Markdown("**Template Guidelines:**")
199
+ gr.Markdown("""
200
+ - Use PNG format for best results
201
+ - Template should have clear panel borders
202
+ - Recommended size: 1024x1024 or higher
203
+ - The AI will use your template as a guide for panel layout
204
+ """)
205
+
206
+ generate_btn = gr.Button("🎨 Generate Manga", variant="primary", size="lg")
207
+
208
+ with gr.Row():
209
+ output_gallery = gr.Gallery(
210
+ label="Generated Manga Panels",
211
+ show_label=True,
212
+ elem_id="gallery",
213
+ columns=2,
214
+ rows=3,
215
+ height="auto"
216
+ )
217
+
218
+ scene_output = gr.Textbox(
219
+ label="Scene Descriptions / Status",
220
+ lines=5,
221
+ max_lines=10
222
+ )
223
+
224
+ with gr.Tab("File Upload"):
225
+ with gr.Row():
226
+ with gr.Column(scale=2):
227
+ file_input = gr.File(
228
+ label="Upload Story File (.txt)",
229
+ file_types=[".txt"]
230
+ )
231
+ with gr.Column(scale=1):
232
+ num_scenes_file = gr.Slider(
233
+ minimum=1,
234
+ maximum=10,
235
+ value=5,
236
+ step=1,
237
+ label="Number of Scenes"
238
+ )
239
+
240
+ # Style Preferences Section for File Upload
241
+ gr.Markdown("### 🎨 Style Preferences")
242
+ with gr.Row():
243
+ with gr.Column():
244
+ art_style_file = gr.Dropdown(
245
+ choices=["None"] + ART_STYLES,
246
+ value="None",
247
+ label="Art Style"
248
+ )
249
+ mood_file = gr.Dropdown(
250
+ choices=["None"] + MOOD_OPTIONS,
251
+ value="None",
252
+ label="Overall Mood"
253
+ )
254
+ color_palette_file = gr.Dropdown(
255
+ choices=["None"] + COLOR_PALETTES,
256
+ value="None",
257
+ label="Color Palette"
258
+ )
259
+ with gr.Column():
260
+ character_style_file = gr.Dropdown(
261
+ choices=["None"] + CHARACTER_STYLES,
262
+ value="None",
263
+ label="Character Style"
264
+ )
265
+ line_style_file = gr.Dropdown(
266
+ choices=["None"] + LINE_STYLES,
267
+ value="None",
268
+ label="Line Art Style"
269
+ )
270
+ composition_file = gr.Dropdown(
271
+ choices=["None"] + COMPOSITION_STYLES,
272
+ value="None",
273
+ label="Composition Style"
274
+ )
275
+
276
+ additional_notes_file = gr.Textbox(
277
+ label="Additional Style Notes",
278
+ placeholder="Any specific style preferences, character descriptions, or artistic directions...",
279
+ lines=3
280
+ )
281
+
282
+ # Custom Template Upload Section for File Upload
283
+ gr.Markdown("### 📋 Custom Template (Optional)")
284
+ gr.Markdown("*Upload your own manga panel template. If not provided, default template will be used.*")
285
+ with gr.Row():
286
+ user_template_file = gr.File(
287
+ label="Upload Custom Template",
288
+ file_types=[".png", ".jpg", ".jpeg", ".webp"]
289
+ )
290
+
291
+ generate_file_btn = gr.Button("🎨 Generate Manga from File", variant="primary", size="lg")
292
+
293
+ with gr.Row():
294
+ output_gallery_file = gr.Gallery(
295
+ label="Generated Manga Panels",
296
+ show_label=True,
297
+ columns=2,
298
+ rows=3,
299
+ height="auto"
300
+ )
301
+
302
+ scene_output_file = gr.Textbox(
303
+ label="Scene Descriptions / Status",
304
+ lines=5,
305
+ max_lines=10
306
+ )
307
+
308
+ with gr.Tab("🔄 Regenerate Panels"):
309
+ gr.Markdown("### Select a panel to regenerate with modifications")
310
+ gr.Markdown("**Note:** You must generate manga first before you can regenerate panels.")
311
+
312
+ with gr.Row():
313
+ with gr.Column(scale=1):
314
+ panel_selector = gr.Number(
315
+ label="Panel Number to Regenerate",
316
+ value=1,
317
+ minimum=1,
318
+ maximum=10,
319
+ precision=0
320
+ )
321
+
322
+ modification_input = gr.Textbox(
323
+ label="Modification Instructions",
324
+ placeholder="e.g., 'Make the lighting more dramatic', 'Change character expression to angry', 'Add more action lines'",
325
+ lines=3
326
+ )
327
+
328
+ # Reference Image Upload Section
329
+ gr.Markdown("### 🖼️ Reference Image (Optional)")
330
+ gr.Markdown("*Upload an image to guide the regeneration style, composition, or specific elements*")
331
+ reference_image_upload = gr.File(
332
+ label="Upload Reference Image",
333
+ file_types=[".png", ".jpg", ".jpeg", ".webp"]
334
+ )
335
+
336
+ replace_checkbox = gr.Checkbox(
337
+ label="Replace original panel",
338
+ value=False
339
+ )
340
+ gr.Markdown("*Check this to replace the original panel in the main gallery*")
341
+
342
+ regenerate_btn = gr.Button("🔄 Regenerate Panel", variant="secondary", size="lg")
343
+
344
+ with gr.Column(scale=2):
345
+ regenerated_image = gr.Image(
346
+ label="Regenerated Panel",
347
+ show_label=True
348
+ )
349
+
350
+ regeneration_status = gr.Textbox(
351
+ label="Status",
352
+ interactive=False
353
+ )
354
+
355
+ # Updated main gallery display (shows when panels are replaced)
356
+ with gr.Row():
357
+ updated_main_gallery = gr.Gallery(
358
+ label="Updated Main Gallery (when replacing panels)",
359
+ show_label=True,
360
+ columns=2,
361
+ rows=3,
362
+ height="auto",
363
+ visible=False
364
+ )
365
+
366
+ gr.Markdown("### Reference Image Guidelines:")
367
+ gr.Markdown("""
368
+ - **Style Reference**: Upload an image with the art style you want to emulate
369
+ - **Composition Reference**: Show the camera angle, pose, or layout you prefer
370
+ - **Color Reference**: Provide color palette or lighting inspiration
371
+ - **Character Reference**: Show specific character appearances or expressions
372
+ - **Environment Reference**: Demonstrate background or setting elements
373
+ """)
374
+
375
+ gr.Markdown("### Common Modification Examples:")
376
+ gr.Markdown("""
377
+ - **Lighting**: "Make it darker with more shadows", "Add bright sunlight", "Create moody twilight atmosphere"
378
+ - **Character**: "Make character look more angry", "Change pose to defensive stance", "Add more detailed facial expression"
379
+ - **Composition**: "Change to close-up shot", "Make it a wide establishing shot", "Add more dynamic camera angle"
380
+ - **Style**: "Add more action lines", "Make it more dramatic", "Simplify the background"
381
+ - **Details**: "Add more environmental details", "Remove background clutter", "Focus more on the character"
382
+ """)
383
+
384
+ with gr.Tab("📥 Download PDF"):
385
+ gr.Markdown("### Export your manga as a PDF")
386
+ gr.Markdown("**Note:** You must generate manga first before you can create a PDF.")
387
+
388
+ with gr.Row():
389
+ with gr.Column(scale=1):
390
+ create_pdf_btn = gr.Button("📥 Create PDF", variant="primary", size="lg")
391
+
392
+ with gr.Column(scale=2):
393
+ pdf_status = gr.Textbox(
394
+ label="Status",
395
+ interactive=False
396
+ )
397
+
398
+ download_pdf = gr.File(
399
+ label="Download PDF",
400
+ visible=False
401
+ )
402
+
403
+ gr.Markdown("### PDF Features:")
404
+ gr.Markdown("""
405
+ - **A4 format** with proper margins and professional layout
406
+ - **One panel per page** with panel numbering
407
+ - **Title page** with manga information
408
+ - **Custom template notation** if user template was used
409
+ - **Automatic image scaling** to fit pages while maintaining aspect ratio
410
+ """)
411
+
412
+ with gr.Tab("🎯 Examples"):
413
+ gr.Markdown("### Explore Example Stories and Manga")
414
+ gr.Markdown("Select from our curated examples to see how stories transform into manga panels!")
415
+
416
+ with gr.Row():
417
+ with gr.Column(scale=1):
418
+ example_selector = gr.Dropdown(
419
+ choices=list(EXAMPLES.keys()),
420
+ label="Select Example",
421
+ value=list(EXAMPLES.keys())[0] if EXAMPLES else None
422
+ )
423
+
424
+ example_title = gr.Textbox(
425
+ label="Story Title",
426
+ interactive=False,
427
+ lines=1
428
+ )
429
+
430
+ example_story = gr.Textbox(
431
+ label="Story Text",
432
+ interactive=False,
433
+ lines=8,
434
+ max_lines=12
435
+ )
436
+
437
+ with gr.Column(scale=2):
438
+ example_gallery = gr.Gallery(
439
+ label="Manga Panels",
440
+ show_label=True,
441
+ columns=2,
442
+ rows=3,
443
+ height="auto"
444
+ )
445
+
446
+ gr.Markdown("### How It Works:")
447
+ gr.Markdown("""
448
+ 1. **Select an Example**: Choose from the dropdown above
449
+ 2. **View the Story**: Read the original story text
450
+ 3. **See the Manga**: Observe how AI transforms text into visual panels
451
+ 4. **Try Your Own**: Use the "Generate Manga" tab to create your own!
452
+ """)
453
+
454
+ # Security notice
455
+ gr.Markdown("---")
456
+ gr.Markdown("### 🔒 Privacy & Security")
457
+ gr.Markdown("""
458
+ - **Your API key is never stored permanently** - it's only kept in memory during your session
459
+ - **All generated content is isolated** - each user gets their own private workspace
460
+ - **Sessions automatically expire** after 24 hours of inactivity
461
+ - **Your files are cleaned up** when your session ends
462
+ """)
463
+
464
+ # Helper functions
465
+ def update_gallery_visibility(gallery_images):
466
+ if gallery_images:
467
+ return gr.Gallery(visible=True, value=gallery_images)
468
+ else:
469
+ return gr.Gallery(visible=False)
470
+
471
+ def create_and_provide_pdf(session_id):
472
+ if session_id is None:
473
+ return "Session not initialized. Please refresh and enter your API key.", gr.File(visible=False)
474
+
475
+ status, pdf_path = create_pdf_interface(session_id)
476
+ if pdf_path:
477
+ return status, gr.File(value=pdf_path, visible=True)
478
+ else:
479
+ return status, gr.File(visible=False)
480
+
481
+ def load_first_example():
482
+ if EXAMPLES:
483
+ first_key = list(EXAMPLES.keys())[0]
484
+ return load_example(first_key)
485
+ return "", "", []
486
+
487
+ # Event handlers
488
+ initialize_btn.click(
489
+ fn=initialize_session,
490
+ inputs=[api_key_input],
491
+ outputs=[session_id_state, session_status, main_tabs]
492
+ )
493
+
494
+ generate_btn.click(
495
+ fn=generate_manga_interface,
496
+ inputs=[session_id_state, story_input, num_scenes_text, art_style_text, mood_text, color_palette_text,
497
+ character_style_text, line_style_text, composition_text, additional_notes_text, user_template_text],
498
+ outputs=[output_gallery, scene_output]
499
+ )
500
+
501
+ generate_file_btn.click(
502
+ fn=generate_manga_from_file_interface,
503
+ inputs=[session_id_state, file_input, num_scenes_file, art_style_file, mood_file, color_palette_file,
504
+ character_style_file, line_style_file, composition_file, additional_notes_file, user_template_file],
505
+ outputs=[output_gallery_file, scene_output_file]
506
+ )
507
+
508
+ regenerate_btn.click(
509
+ fn=regenerate_and_replace_interface,
510
+ inputs=[session_id_state, panel_selector, modification_input, replace_checkbox, reference_image_upload],
511
+ outputs=[regeneration_status, regenerated_image, updated_main_gallery]
512
+ ).then(
513
+ fn=update_gallery_visibility,
514
+ inputs=[updated_main_gallery],
515
+ outputs=[updated_main_gallery]
516
+ )
517
+
518
+ create_pdf_btn.click(
519
+ fn=create_and_provide_pdf,
520
+ inputs=[session_id_state],
521
+ outputs=[pdf_status, download_pdf]
522
+ )
523
+
524
+ # Example selection
525
+ example_selector.change(
526
+ fn=load_example,
527
+ inputs=[example_selector],
528
+ outputs=[example_title, example_story, example_gallery]
529
+ )
530
+
531
+ # Load first example on startup
532
+ demo.load(
533
+ fn=load_first_example,
534
+ outputs=[example_title, example_story, example_gallery]
535
+ )
536
+
537
+ return demo
538
+
539
+ if __name__ == "__main__":
540
+ demo = create_gradio_interface()
541
+ demo.launch(share=True)
data/stories/example_story.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ In a quiet village surrounded by hills, there stood an ancient oak tree at the edge of the forest. The villagers said it was magical—that if you whispered your wish to its trunk, the tree would carry it to the stars.
2
+
3
+ Nine-year-old Nora didn’t believe in magic, but she loved stories. One evening, as the sky turned purple and the fireflies came out, she tiptoed to the tree with a secret wish. “I want to find a friend who understands me,” she whispered, pressing her cheek against the rough bark.
4
+
5
+ The next day, while walking through the woods, she heard soft humming. Following the sound, she found a small fox with bright amber eyes, tangled in some vines. Without thinking, she freed it gently. The fox didn’t run away but sat beside her, tail wagging.
6
+
7
+ From that day on, Nora and the fox became inseparable. They explored streams, climbed hills, and lay beneath the stars sharing dreams. No one else saw the fox, but Nora didn’t mind.
8
+
9
+ On clear nights, she would sit by the oak tree and thank it. The tree’s leaves rustled in response, as if smiling.
10
+
11
+ The villagers still whispered about the magic tree, but Nora knew the truth—it wasn’t about spells or charms. Sometimes, all it takes is a wish, a little courage, and a heart open enough to hear the world’s softest whispers.
data/templates/template.png ADDED
manga.py ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import PIL
3
+ from io import BytesIO
4
+ from google import genai
5
+ from dotenv import load_dotenv
6
+ import pathlib
7
+ from utils import (
8
+ get_scene_splitting_prompt,
9
+ get_panel_prompt,
10
+ SCENE_BREAK_DELIMITER,
11
+ get_regeneration_prompt
12
+ )
13
+ import time
14
+ from PIL import Image
15
+ from reportlab.pdfgen import canvas
16
+ from reportlab.lib.pagesizes import A4
17
+ import shutil
18
+ import uuid
19
+ import hashlib
20
+ from typing import Dict, Optional
21
+
22
+ # Load environment variables
23
+ load_dotenv()
24
+
25
+ class UserSession:
26
+ """Represents a user session with isolated data and API key."""
27
+
28
+ def __init__(self, session_id: str, api_key: str):
29
+ self.session_id = session_id
30
+ self.api_key_hash = self._hash_api_key(api_key) # Store only hash for security
31
+ self._api_key = api_key # Keep encrypted or in memory only
32
+ self.created_at = time.time()
33
+ self.last_activity = time.time()
34
+
35
+ # Session-specific directories
36
+ self.base_dir = os.path.join("data", "sessions", session_id)
37
+ self.output_dir = os.path.join(self.base_dir, "output")
38
+ self.user_templates_dir = os.path.join(self.base_dir, "templates")
39
+ self.user_references_dir = os.path.join(self.base_dir, "references")
40
+
41
+ # Create session directories
42
+ os.makedirs(self.output_dir, exist_ok=True)
43
+ os.makedirs(self.user_templates_dir, exist_ok=True)
44
+ os.makedirs(self.user_references_dir, exist_ok=True)
45
+
46
+ # Session generation data
47
+ self.current_generation = {
48
+ 'scenes': [],
49
+ 'generated_images': [],
50
+ 'chat': None,
51
+ 'user_preferences': {},
52
+ 'current_template_path': os.getenv("TEMPLATE_PATH", "data/templates/template.png")
53
+ }
54
+
55
+ def _hash_api_key(self, api_key: str) -> str:
56
+ """Create a hash of the API key for identification (not storage)."""
57
+ return hashlib.sha256(api_key.encode()).hexdigest()[:16]
58
+
59
+ def get_api_key(self) -> str:
60
+ """Get the API key (should be implemented with proper encryption in production)."""
61
+ return self._api_key
62
+
63
+ def update_activity(self):
64
+ """Update last activity timestamp."""
65
+ self.last_activity = time.time()
66
+
67
+ def is_expired(self, timeout_hours: int = 24) -> bool:
68
+ """Check if session has expired."""
69
+ return (time.time() - self.last_activity) > (timeout_hours * 3600)
70
+
71
+ def cleanup(self):
72
+ """Clean up session files and data."""
73
+ try:
74
+ if os.path.exists(self.base_dir):
75
+ shutil.rmtree(self.base_dir)
76
+ print(f"Cleaned up session: {self.session_id}")
77
+ except Exception as e:
78
+ print(f"Error cleaning up session {self.session_id}: {e}")
79
+
80
+
81
+ class SessionManager:
82
+ """Manages user sessions and provides isolation between users."""
83
+
84
+ def __init__(self):
85
+ self.sessions: Dict[str, UserSession] = {}
86
+ self.session_timeout_hours = 24
87
+
88
+ def create_session(self, api_key: str) -> str:
89
+ """Create a new session for a user with their API key."""
90
+ session_id = str(uuid.uuid4())
91
+ self.sessions[session_id] = UserSession(session_id, api_key)
92
+ self._cleanup_expired_sessions()
93
+ return session_id
94
+
95
+ def get_session(self, session_id: str) -> Optional[UserSession]:
96
+ """Get a session by ID, return None if not found or expired."""
97
+ if session_id not in self.sessions:
98
+ return None
99
+
100
+ session = self.sessions[session_id]
101
+ if session.is_expired(self.session_timeout_hours):
102
+ self._remove_session(session_id)
103
+ return None
104
+
105
+ session.update_activity()
106
+ return session
107
+
108
+ def _remove_session(self, session_id: str):
109
+ """Remove and cleanup a session."""
110
+ if session_id in self.sessions:
111
+ self.sessions[session_id].cleanup()
112
+ del self.sessions[session_id]
113
+
114
+ def _cleanup_expired_sessions(self):
115
+ """Clean up all expired sessions."""
116
+ expired_sessions = [
117
+ sid for sid, session in self.sessions.items()
118
+ if session.is_expired(self.session_timeout_hours)
119
+ ]
120
+ for session_id in expired_sessions:
121
+ self._remove_session(session_id)
122
+
123
+
124
+ class MangaGenerator:
125
+ def __init__(self, session: UserSession):
126
+ """Initialize the manga generator for a specific user session."""
127
+ self.session = session
128
+ self.api_key = session.get_api_key()
129
+
130
+ # Use session-specific directories
131
+ self.template_path = os.getenv("TEMPLATE_PATH", "data/templates/template.png")
132
+ self.output_dir = session.output_dir
133
+ self.user_templates_dir = session.user_templates_dir
134
+ self.user_references_dir = session.user_references_dir
135
+
136
+ self.image_gen_model_name = os.getenv("IMAGE_MODEL_NAME", "gemini-2.5-flash-image-preview")
137
+ self.scene_gen_model_name = os.getenv("SCENE_MODEL_NAME", "gemini-2.0-flash")
138
+
139
+ # Initialize clients with session's API key
140
+ try:
141
+ self.image_gen_client = genai.Client(api_key=self.api_key)
142
+ self.scene_gen_client = genai.Client(api_key=self.api_key)
143
+ except Exception as e:
144
+ raise ValueError(f"Invalid API key or client initialization failed: {e}")
145
+
146
+ @property
147
+ def current_generation(self):
148
+ """Access current generation data from session."""
149
+ return self.session.current_generation
150
+
151
+ def save_user_template(self, uploaded_file):
152
+ """Save user uploaded template and return the path."""
153
+ if uploaded_file is None:
154
+ return None
155
+
156
+ try:
157
+ # Generate unique filename with timestamp
158
+ timestamp = int(time.time())
159
+ filename = f"user_template_{timestamp}.png"
160
+ template_path = os.path.join(self.user_templates_dir, filename)
161
+
162
+ # Handle different file input types
163
+ if hasattr(uploaded_file, 'name'): # Gradio file object
164
+ shutil.copy2(uploaded_file.name, template_path)
165
+ else:
166
+ if isinstance(uploaded_file, str):
167
+ shutil.copy2(uploaded_file, template_path)
168
+ elif hasattr(uploaded_file, 'save'): # PIL Image
169
+ uploaded_file.save(template_path)
170
+
171
+ # Verify the template was saved and is a valid image
172
+ test_image = PIL.Image.open(template_path)
173
+ test_image.verify()
174
+
175
+ print(f"User template saved to: {template_path}")
176
+ return template_path
177
+
178
+ except Exception as e:
179
+ print(f"Error saving user template: {e}")
180
+ return None
181
+
182
+ def save_user_reference_image(self, uploaded_file):
183
+ """Save user uploaded reference image and return the path."""
184
+ if uploaded_file is None:
185
+ return None
186
+
187
+ try:
188
+ timestamp = int(time.time())
189
+ filename = f"user_reference_{timestamp}.png"
190
+ reference_path = os.path.join(self.user_references_dir, filename)
191
+
192
+ if hasattr(uploaded_file, 'name'): # Gradio file object
193
+ shutil.copy2(uploaded_file.name, reference_path)
194
+ else:
195
+ if isinstance(uploaded_file, str):
196
+ shutil.copy2(uploaded_file, reference_path)
197
+ elif hasattr(uploaded_file, 'save'): # PIL Image
198
+ uploaded_file.save(reference_path)
199
+
200
+ test_image = PIL.Image.open(reference_path)
201
+ test_image.verify()
202
+
203
+ print(f"User reference image saved to: {reference_path}")
204
+ return reference_path
205
+
206
+ except Exception as e:
207
+ print(f"Error saving user reference image: {e}")
208
+ return None
209
+
210
+ def set_template_for_generation(self, template_path):
211
+ """Set the template to use for the current generation session."""
212
+ if template_path and os.path.exists(template_path):
213
+ self.current_generation['current_template_path'] = template_path
214
+ return True
215
+ return False
216
+
217
+ def get_current_template_path(self):
218
+ """Get the current template path being used."""
219
+ return self.current_generation.get('current_template_path', self.template_path)
220
+
221
+ def read_story(self, file_path):
222
+ """Read story text from file."""
223
+ with open(file_path, "r") as f:
224
+ return f.read()
225
+
226
+ def split_into_scenes(self, story_text: str, n_scenes: int):
227
+ """Split story into visual scenes with descriptions."""
228
+ prompt = get_scene_splitting_prompt(story_text, n_scenes)
229
+
230
+ response = self.scene_gen_client.models.generate_content(
231
+ model=self.scene_gen_model_name,
232
+ contents=[prompt]
233
+ )
234
+
235
+ full_response_text = ""
236
+ for part in response.candidates[0].content.parts:
237
+ if part.text:
238
+ full_response_text += part.text
239
+
240
+ scenes = [scene.strip() for scene in full_response_text.split(SCENE_BREAK_DELIMITER)]
241
+ scenes = [scene for scene in scenes if scene]
242
+
243
+ return scenes[:n_scenes]
244
+
245
+ def save_image(self, response, path):
246
+ """Save the generated image from response."""
247
+ time.sleep(3)
248
+ for part in response.parts:
249
+ if image := part.as_image():
250
+ image.save(path)
251
+ return image
252
+ return None
253
+
254
+ def generate_image_for_scene(self, scene_description: str, output_path: str):
255
+ """Generate image for a single scene."""
256
+ current_template = self.get_current_template_path()
257
+ response = self.image_gen_client.models.generate_content(
258
+ model=self.image_gen_model_name,
259
+ contents=[
260
+ scene_description,
261
+ PIL.Image.open(current_template)
262
+ ]
263
+ )
264
+
265
+ saved_image = self.save_image(response, output_path)
266
+ return response, saved_image
267
+
268
+ def generate_image_with_chat(self, scene_description: str, output_path: str, chat):
269
+ """Generate image using chat context for consistency."""
270
+ current_template = self.get_current_template_path()
271
+ response = chat.send_message([
272
+ scene_description,
273
+ PIL.Image.open(current_template)
274
+ ])
275
+
276
+ saved_image = self.save_image(response, output_path)
277
+ return response, saved_image
278
+
279
+ def generate_image_with_chat_and_reference(self, scene_description: str, output_path: str, chat, reference_image_path=None):
280
+ """Generate image using chat context with optional reference image."""
281
+ current_template = self.get_current_template_path()
282
+
283
+ content = [scene_description, PIL.Image.open(current_template)]
284
+
285
+ if reference_image_path and os.path.exists(reference_image_path):
286
+ content.append(PIL.Image.open(reference_image_path))
287
+ print(f"Using reference image: {reference_image_path}")
288
+
289
+ response = chat.send_message(content)
290
+ saved_image = self.save_image(response, output_path)
291
+ return response, saved_image
292
+
293
+ def regenerate_specific_panel(self, panel_index: int, modification_request: str, reference_image=None):
294
+ """Regenerate a specific panel with modifications and optional reference image."""
295
+ if not self.current_generation['scenes'] or not self.current_generation['chat']:
296
+ raise ValueError("No active generation session. Please generate manga first.")
297
+
298
+ if panel_index >= len(self.current_generation['scenes']):
299
+ raise ValueError(f"Panel index {panel_index} is out of range.")
300
+
301
+ original_scene = self.current_generation['scenes'][panel_index]
302
+
303
+ reference_image_path = None
304
+ if reference_image is not None:
305
+ reference_image_path = self.save_user_reference_image(reference_image)
306
+ if reference_image_path:
307
+ modification_request += "\n\nIMPORTANT: Use the provided reference image as visual guidance for style, composition, or specific elements while maintaining the story's integrity."
308
+
309
+ user_preferences = self.current_generation.get('user_preferences', {})
310
+ modified_prompt = get_regeneration_prompt(
311
+ original_scene,
312
+ modification_request,
313
+ is_first_panel=(panel_index == 0),
314
+ user_preferences=user_preferences
315
+ )
316
+
317
+ current_version = self.current_generation['generated_images'][panel_index].get('version', 1)
318
+ output_path = os.path.join(self.output_dir, f"scene{panel_index+1}_v{current_version + 1}.png")
319
+
320
+ response, saved_image = self.generate_image_with_chat_and_reference(
321
+ modified_prompt,
322
+ output_path,
323
+ self.current_generation['chat'],
324
+ reference_image_path
325
+ )
326
+
327
+ return output_path, saved_image
328
+
329
+ def replace_panel(self, panel_index: int, new_image_path: str, new_image: PIL.Image):
330
+ """Replace a panel in the current generation."""
331
+ if not self.current_generation['generated_images']:
332
+ raise ValueError("No active generation session.")
333
+
334
+ if panel_index >= len(self.current_generation['generated_images']):
335
+ raise ValueError(f"Panel index {panel_index} is out of range.")
336
+
337
+ current_version = self.current_generation['generated_images'][panel_index].get('version', 1)
338
+ self.current_generation['generated_images'][panel_index].update({
339
+ 'image_path': new_image_path,
340
+ 'image': new_image,
341
+ 'version': current_version + 1
342
+ })
343
+
344
+ def get_current_gallery_paths(self):
345
+ """Get current image paths for gallery display."""
346
+ if not self.current_generation['generated_images']:
347
+ return []
348
+ return [img['image_path'] for img in self.current_generation['generated_images']]
349
+
350
+ def generate_manga_from_story(self, story_text: str, n_scenes: int = 5, user_preferences: dict = None, user_template=None):
351
+ """Generate complete manga from story text with user preferences and optional custom template."""
352
+ if user_template is not None:
353
+ template_path = self.save_user_template(user_template)
354
+ if template_path:
355
+ self.set_template_for_generation(template_path)
356
+ print(f"Using user template: {template_path}")
357
+ else:
358
+ print("Failed to save user template, using default template")
359
+
360
+ if user_preferences is None:
361
+ user_preferences = {}
362
+
363
+ scenes = self.split_into_scenes(story_text, n_scenes)
364
+
365
+ chat = self.image_gen_client.chats.create(model=self.image_gen_model_name)
366
+
367
+ generated_images = []
368
+ responses = []
369
+
370
+ for i, scene in enumerate(scenes):
371
+ scene_description = get_panel_prompt(scene, is_first_panel=(i == 0), user_preferences=user_preferences)
372
+
373
+ output_path = os.path.join(self.output_dir, f"scene{i+1}.png")
374
+ response, saved_image = self.generate_image_with_chat(scene_description, output_path, chat)
375
+
376
+ responses.append(response)
377
+ generated_images.append({
378
+ 'scene_number': i + 1,
379
+ 'scene_text': scene,
380
+ 'image_path': output_path,
381
+ 'image': saved_image,
382
+ 'version': 1
383
+ })
384
+
385
+ print(f"Generated scene {i+1}")
386
+
387
+ self.current_generation.update({
388
+ 'scenes': scenes,
389
+ 'generated_images': generated_images,
390
+ 'chat': chat,
391
+ 'user_preferences': user_preferences
392
+ })
393
+
394
+ return generated_images, scenes
395
+
396
+ def generate_manga_from_file(self, story_file_path: str, n_scenes: int = 5, user_preferences: dict = None, user_template=None):
397
+ """Generate manga from story file with user preferences and optional custom template."""
398
+ story_text = self.read_story(story_file_path)
399
+ return self.generate_manga_from_story(story_text, n_scenes, user_preferences, user_template)
400
+
401
+ def get_current_panels(self):
402
+ """Get current panel information for the interface."""
403
+ if not self.current_generation['generated_images']:
404
+ return []
405
+
406
+ return [(i+1, img['image_path']) for i, img in enumerate(self.current_generation['generated_images'])]
407
+
408
+ def create_manga_pdf(self, output_filename=None):
409
+ """Create a PDF file from all current manga panels."""
410
+ if not self.current_generation['generated_images']:
411
+ raise ValueError("No manga panels to export. Please generate manga first.")
412
+
413
+ if output_filename is None:
414
+ output_filename = os.path.join(self.output_dir, "manga_complete.pdf")
415
+
416
+ c = canvas.Canvas(output_filename, pagesize=A4)
417
+ page_width, page_height = A4
418
+
419
+ c.setFont("Helvetica-Bold", 24)
420
+ title_text = "Generated Manga"
421
+ title_width = c.stringWidth(title_text, "Helvetica-Bold", 24)
422
+ c.drawString((page_width - title_width) / 2, page_height - 100, title_text)
423
+
424
+ c.setFont("Helvetica", 12)
425
+ subtitle_text = f"Total Panels: {len(self.current_generation['generated_images'])}"
426
+ subtitle_width = c.stringWidth(subtitle_text, "Helvetica", 12)
427
+ c.drawString((page_width - subtitle_width) / 2, page_height - 130, subtitle_text)
428
+
429
+ current_template = self.get_current_template_path()
430
+ if current_template != self.template_path:
431
+ template_info = "Custom Template Used"
432
+ template_width = c.stringWidth(template_info, "Helvetica", 12)
433
+ c.drawString((page_width - template_width) / 2, page_height - 150, template_info)
434
+
435
+ c.showPage()
436
+
437
+ for i, panel_data in enumerate(self.current_generation['generated_images']):
438
+ image_path = panel_data['image_path']
439
+
440
+ if os.path.exists(image_path):
441
+ img = Image.open(image_path)
442
+
443
+ img_width, img_height = img.size
444
+ aspect_ratio = img_width / img_height
445
+
446
+ max_width = page_width - 100
447
+ max_height = page_height - 150
448
+
449
+ if aspect_ratio > 1:
450
+ new_width = min(max_width, img_width)
451
+ new_height = new_width / aspect_ratio
452
+ else:
453
+ new_height = min(max_height, img_height)
454
+ new_width = new_height * aspect_ratio
455
+
456
+ x = (page_width - new_width) / 2
457
+ y = (page_height - new_height) / 2
458
+
459
+ c.drawImage(image_path, x, y, width=new_width, height=new_height)
460
+
461
+ c.setFont("Helvetica", 10)
462
+ c.drawString(50, page_height - 30, f"Panel {i + 1}")
463
+
464
+ c.showPage()
465
+
466
+ c.save()
467
+ print(f"PDF saved to: {output_filename}")
468
+ return output_filename
469
+
470
+
471
+ # Global session manager
472
+ _session_manager = SessionManager()
473
+
474
+ def get_session_manager() -> SessionManager:
475
+ """Get the global session manager."""
476
+ return _session_manager
477
+
478
+ def get_generator_for_session(session_id: str) -> Optional[MangaGenerator]:
479
+ """Get a manga generator for a specific session."""
480
+ session = _session_manager.get_session(session_id)
481
+ if session is None:
482
+ return None
483
+ return MangaGenerator(session)
484
+
485
+ def create_user_session(api_key: str) -> str:
486
+ """Create a new user session with API key."""
487
+ if not api_key or not api_key.strip():
488
+ raise ValueError("API key is required")
489
+ return _session_manager.create_session(api_key.strip())
490
+
491
+ # Interface functions with session support
492
+ def generate_manga_interface(session_id: str, story_text: str, num_scenes: int = 5, art_style: str = None, mood: str = None,
493
+ color_palette: str = None, character_style: str = None, line_style: str = None,
494
+ composition: str = None, additional_notes: str = "", user_template=None):
495
+ """Interface function for Gradio - generates manga from text input with session support."""
496
+ try:
497
+ generator = get_generator_for_session(session_id)
498
+ if generator is None:
499
+ return [], "Session expired or invalid. Please refresh and enter your API key again."
500
+
501
+ user_preferences = {}
502
+ if art_style and art_style != "None":
503
+ user_preferences['art_style'] = art_style
504
+ if mood and mood != "None":
505
+ user_preferences['mood'] = mood
506
+ if color_palette and color_palette != "None":
507
+ user_preferences['color_palette'] = color_palette
508
+ if character_style and character_style != "None":
509
+ user_preferences['character_style'] = character_style
510
+ if line_style and line_style != "None":
511
+ user_preferences['line_style'] = line_style
512
+ if composition and composition != "None":
513
+ user_preferences['composition'] = composition
514
+ if additional_notes.strip():
515
+ user_preferences['additional_notes'] = additional_notes.strip()
516
+
517
+ generated_images, scenes = generator.generate_manga_from_story(story_text, num_scenes, user_preferences, user_template)
518
+
519
+ image_paths = [img['image_path'] for img in generated_images]
520
+ scene_descriptions = [img['scene_text'] for img in generated_images]
521
+
522
+ return image_paths, "\n\n".join([f"Scene {i+1}: {scene}" for i, scene in enumerate(scene_descriptions)])
523
+ except Exception as e:
524
+ print(f"Error generating manga: {e}")
525
+ return [], f"Error: {str(e)}"
526
+
527
+ def generate_manga_from_file_interface(session_id: str, story_file, num_scenes: int = 5, art_style: str = None, mood: str = None,
528
+ color_palette: str = None, character_style: str = None, line_style: str = None,
529
+ composition: str = None, additional_notes: str = "", user_template=None):
530
+ """Interface function for Gradio - generates manga from uploaded file with session support."""
531
+ try:
532
+ generator = get_generator_for_session(session_id)
533
+ if generator is None:
534
+ return [], "Session expired or invalid. Please refresh and enter your API key again."
535
+
536
+ if hasattr(story_file, 'name'): # Gradio file object
537
+ with open(story_file.name, 'r') as f:
538
+ story_text = f.read()
539
+ else:
540
+ story_text = str(story_file)
541
+
542
+ user_preferences = {}
543
+ if art_style and art_style != "None":
544
+ user_preferences['art_style'] = art_style
545
+ if mood and mood != "None":
546
+ user_preferences['mood'] = mood
547
+ if color_palette and color_palette != "None":
548
+ user_preferences['color_palette'] = color_palette
549
+ if character_style and character_style != "None":
550
+ user_preferences['character_style'] = character_style
551
+ if line_style and line_style != "None":
552
+ user_preferences['line_style'] = line_style
553
+ if composition and composition != "None":
554
+ user_preferences['composition'] = composition
555
+ if additional_notes.strip():
556
+ user_preferences['additional_notes'] = additional_notes.strip()
557
+
558
+ generated_images, scenes = generator.generate_manga_from_story(story_text, num_scenes, user_preferences, user_template)
559
+
560
+ image_paths = [img['image_path'] for img in generated_images]
561
+ scene_descriptions = [img['scene_text'] for img in generated_images]
562
+
563
+ return image_paths, "\n\n".join([f"Scene {i+1}: {scene}" for i, scene in enumerate(scene_descriptions)])
564
+ except Exception as e:
565
+ print(f"Error generating manga: {e}")
566
+ return [], f"Error: {str(e)}"
567
+
568
+ def regenerate_and_replace_interface(session_id: str, panel_number: int, modification_request: str, replace_original: bool, reference_image=None):
569
+ """Interface function for Gradio - regenerate panel with session support."""
570
+ try:
571
+ generator = get_generator_for_session(session_id)
572
+ if generator is None:
573
+ return "Session expired or invalid. Please refresh and enter your API key again.", None, []
574
+
575
+ if not modification_request.strip():
576
+ return "Please provide modification instructions.", None, []
577
+
578
+ panel_index = panel_number - 1
579
+ new_image_path, saved_image = generator.regenerate_specific_panel(panel_index, modification_request, reference_image)
580
+
581
+ updated_gallery = []
582
+ if replace_original and generator.current_generation['generated_images']:
583
+ generator.replace_panel(panel_index, new_image_path, saved_image)
584
+ updated_gallery = generator.get_current_gallery_paths()
585
+ status_message = f"Panel {panel_number} regenerated and replaced successfully!"
586
+ else:
587
+ status_message = f"Panel {panel_number} regenerated successfully! (Original preserved)"
588
+
589
+ return status_message, new_image_path, updated_gallery
590
+
591
+ except Exception as e:
592
+ return f"Error regenerating panel: {e}", None, []
593
+
594
+ def create_pdf_interface(session_id: str):
595
+ """Interface function for Gradio - create PDF with session support."""
596
+ try:
597
+ generator = get_generator_for_session(session_id)
598
+ if generator is None:
599
+ return "Session expired or invalid. Please refresh and enter your API key again.", None
600
+
601
+ if not generator.current_generation['generated_images']:
602
+ return "No manga panels to export. Please generate manga first.", None
603
+
604
+ pdf_path = generator.create_manga_pdf()
605
+ message = f"PDF created successfully! ({len(generator.current_generation['generated_images'])} panels)"
606
+
607
+ return message, pdf_path
608
+ except Exception as e:
609
+ return f"Error creating PDF: {e}", None
610
+
611
+ def get_current_panels(session_id: str):
612
+ """Get current panel information for the interface with session support."""
613
+ generator = get_generator_for_session(session_id)
614
+ if generator is None:
615
+ return []
616
+ return generator.get_current_panels()
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "MangakAI"
3
+ version = "0.1.0"
4
+ description = "Transform your imagination into visual stories with the power of AI!"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "google-genai>=1.33.0",
9
+ "gradio>=5.44.1",
10
+ "pillow>=11.3.0",
11
+ "python-dotenv>=1.1.1",
12
+ "reportlab>=4.4.3",
13
+ ]
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ google-genai>=1.33.0
2
+ gradio>=5.44.1
3
+ pillow>=11.3.0
4
+ python-dotenv>=1.1.1
5
+ reportlab>=4.4.3
utils.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility functions and prompt templates for manga generation.
3
+ """
4
+
5
+ def get_scene_splitting_prompt(story_text: str, n_scenes: int) -> str:
6
+ """Generate the prompt for splitting a story into scenes."""
7
+ return f"""
8
+ Act as a master film director and veteran comic book artist, with an exceptional eye for cinematic composition and dramatic effect. Your task is to read the following story and transmute it into {n_scenes} distinct, visually-dense scene descriptions, including key dialogues and a reference to the original text.
9
+
10
+ The goal is to create a powerful visual description for each scene that can be used directly as a prompt for an AI image generator, while also capturing the most important lines spoken and preserving the source text.
11
+
12
+ **STORY:**
13
+ {story_text}
14
+
15
+ ---
16
+
17
+ **CRITICAL INSTRUCTIONS FOR EACH SCENE'S DESCRIPTION:**
18
+ - **Reference the Source:** In the 'Original Scene Text' field, you must include the exact, unaltered passage from the story that you are adapting for this scene.
19
+ - **Be the Camera:** Describe the scene as if looking through a camera lens. Explicitly mention the shot type (e.g., wide shot, extreme close-up, over-the-shoulder), camera angle, and the composition of elements.
20
+ - **Paint the Atmosphere:** Weave together the setting, time of day, weather, and mood. Use lighting (e.g., "harsh neon glare," "soft morning light," "dramatic chiaroscuro shadows") to establish the tone.
21
+ - **Focus on the Subject:** Detail the characters' specific poses, actions, and intense facial expressions. Describe their clothing and how it interacts with their actions and the environment (e.g., "a rain-soaked cloak clinging to their frame," "wind-whipped hair").
22
+ - **Emphasize the Core Moment:** Ensure the description builds towards the single most important visual element, action, or emotional beat in the scene.
23
+ - **Isolate Key Dialogue:** If the scene contains crucial, plot-advancing dialogue, extract the most impactful line(s) verbatim.
24
+
25
+ **OUTPUT FORMAT:**
26
+ You must use the following format. Separate each scene block with '---SCENE_BREAK---'.
27
+
28
+ **Scene:** [Scene Number]
29
+ **Original Scene Text:** [The exact, unaltered passage from the story for this scene.]
30
+ **Visual Description:** [A single, comprehensive paragraph combining all the visual instructions. This must be a self-contained prompt for an image model. **DO NOT include dialogue here**.]
31
+ **Key Dialogue:** [The most important line(s) of dialogue from the scene. If there is no dialogue, write 'None'.]
32
+ ---SCENE_BREAK---
33
+ """
34
+
35
+ def get_first_panel_prompt(scene: str, user_preferences: dict = None) -> str:
36
+ """Generate the prompt for the first manga panel."""
37
+ style_instructions = get_style_instructions(user_preferences) if user_preferences else ""
38
+
39
+ return f"""
40
+ **Act as a master mangaka creating the first panel of a new manga.**
41
+
42
+ **YOUR TASK:** Create a visually stunning manga page based on the scene description below. You have two creative options:
43
+
44
+ 1. **USE A TEMPLATE:** Choose ONE of the three provided panel templates. Your illustration must perfectly integrate into the chosen panel's borders, using its layout as the foundation for a dynamic composition.
45
+
46
+ 2. **CREATE A SPLASH PAGE:** If the scene is a powerful establishing shot or a highly dramatic moment, you may ignore the templates and create a full-page, borderless splash image that captures the full impact of the scene.
47
+
48
+ **CRITICAL:** You must adhere strictly to the visual details in the scene description.
49
+ - **Style:** The art style, line weight, and shading must remain consistent.
50
+ - **Original Story:** Do not deviate from the original story text. Every scene must accurately reflect the source material as well.
51
+ - **Ensure that there is no repetition of scenes. Each panel must advance the story.**
52
+ - **If the scene contains dialogue, include the exact lines in speech bubbles.**
53
+ - **Create multiple panels per page to maintain pacing.**
54
+ - **Include good story narration boxes so the reader can follow the story.**
55
+
56
+ {style_instructions}
57
+
58
+ ---
59
+ **SCENE DESCRIPTION:**
60
+ {scene}
61
+ """
62
+
63
+ def get_subsequent_panel_prompt(scene: str, user_preferences: dict = None) -> str:
64
+ """Generate the prompt for subsequent manga panels."""
65
+ style_instructions = get_style_instructions(user_preferences) if user_preferences else ""
66
+
67
+ return f"""
68
+ **Act as a master mangaka continuing a manga sequence.**
69
+
70
+ **YOUR TASK:** Create the next manga page, ensuring it flows perfectly from the previous panel. You have two creative options:
71
+
72
+ 1. **USE A TEMPLATE:** Choose ONE of the three provided panel templates to best fit the action. Your illustration must integrate perfectly into the chosen panel's borders.
73
+
74
+ 2. **CREATE A SPLASH PAGE:** If this scene is a major climax or a dramatic shift, you may ignore the templates and create a full-page, borderless splash image.
75
+
76
+ **CRITICAL - MAINTAIN VISUAL CONTINUITY:**
77
+ - **Characters:** Must have the exact same appearance, clothing, and features as the previous panel.
78
+ - **Style:** The art style, line weight, and shading must remain consistent.
79
+ - **Environment:** The setting and lighting must logically follow the previous panel.
80
+ - **Original Story:** Do not deviate from the original story text. Every scene must accurately reflect the source material as well.
81
+ - **Ensure that there is no repetition of scenes. Each panel must advance the story.**
82
+ - **If the scene contains dialogue, include the exact lines in speech bubbles.**
83
+ - **Create multiple panels per page to maintain pacing.**
84
+ - **Include good story narration boxes so the reader can follow the story.**
85
+
86
+ {style_instructions}
87
+
88
+ ---
89
+ **SCENE DESCRIPTION:**
90
+ {scene}
91
+ """
92
+
93
+ def get_panel_prompt(scene: str, is_first_panel: bool = False, user_preferences: dict = None) -> str:
94
+ """Get the appropriate panel prompt based on whether it's the first panel or not."""
95
+ if is_first_panel:
96
+ return get_first_panel_prompt(scene, user_preferences)
97
+ else:
98
+ return get_subsequent_panel_prompt(scene, user_preferences)
99
+
100
+ def get_regeneration_prompt(original_scene: str, modification_request: str, is_first_panel: bool = False, user_preferences: dict = None) -> str:
101
+ """Generate prompt for regenerating a panel with modifications."""
102
+ base_instruction = get_first_panel_prompt(original_scene, user_preferences) if is_first_panel else get_subsequent_panel_prompt(original_scene, user_preferences)
103
+
104
+ return f"""
105
+ {base_instruction}
106
+
107
+ ---
108
+ **MODIFICATION REQUEST:**
109
+ The user has requested the following changes to this panel:
110
+ {modification_request}
111
+
112
+ **CRITICAL:** Incorporate these modifications while maintaining all other aspects of the original scene description and ensuring visual continuity with the manga sequence.
113
+ """
114
+
115
+ def get_style_instructions(user_preferences: dict) -> str:
116
+ """Generate style instructions based on user preferences."""
117
+ if not user_preferences:
118
+ return ""
119
+
120
+ instructions = "\n**USER STYLE PREFERENCES:**"
121
+
122
+ if user_preferences.get('art_style'):
123
+ instructions += f"\n- **Art Style:** {user_preferences['art_style']}"
124
+
125
+ if user_preferences.get('mood'):
126
+ instructions += f"\n- **Overall Mood:** {user_preferences['mood']}"
127
+
128
+ if user_preferences.get('color_palette'):
129
+ instructions += f"\n- **Color Palette:** {user_preferences['color_palette']}"
130
+
131
+ if user_preferences.get('character_style'):
132
+ instructions += f"\n- **Character Design:** {user_preferences['character_style']}"
133
+
134
+ if user_preferences.get('line_style'):
135
+ instructions += f"\n- **Line Art Style:** {user_preferences['line_style']}"
136
+
137
+ if user_preferences.get('composition'):
138
+ instructions += f"\n- **Composition Preference:** {user_preferences['composition']}"
139
+
140
+ if user_preferences.get('additional_notes'):
141
+ instructions += f"\n- **Additional Notes:** {user_preferences['additional_notes']}"
142
+
143
+ instructions += "\n\n**CRITICAL:** Incorporate ALL these style preferences while maintaining the story integrity, visual continuity, and all the requirements above."
144
+
145
+ return instructions
146
+
147
+ # Constants
148
+ SCENE_BREAK_DELIMITER = "---SCENE_BREAK---"
149
+
150
+ # Pre-defined style options for the interface
151
+ ART_STYLES = [
152
+ "Traditional Manga/Anime",
153
+ "Shonen (Bold, Dynamic)",
154
+ "Shoujo (Soft, Romantic)",
155
+ "Seinen (Mature, Detailed)",
156
+ "Chibi (Cute, Simplified)",
157
+ "Realistic",
158
+ "Semi-Realistic",
159
+ "Minimalist",
160
+ "Dark/Gothic",
161
+ "Cyberpunk",
162
+ "Fantasy",
163
+ "Horror",
164
+ "Comedy/Cartoon"
165
+ ]
166
+
167
+ MOOD_OPTIONS = [
168
+ "Epic/Heroic",
169
+ "Dark/Mysterious",
170
+ "Light/Cheerful",
171
+ "Dramatic/Intense",
172
+ "Romantic",
173
+ "Action-Packed",
174
+ "Peaceful/Serene",
175
+ "Suspenseful",
176
+ "Melancholic",
177
+ "Whimsical"
178
+ ]
179
+
180
+ COLOR_PALETTES = [
181
+ "Full Color",
182
+ "Black and White",
183
+ "Sepia/Vintage",
184
+ "Monochromatic Blue",
185
+ "Monochromatic Red",
186
+ "Warm Tones",
187
+ "Cool Tones",
188
+ "High Contrast",
189
+ "Pastel Colors",
190
+ "Neon/Vibrant"
191
+ ]
192
+
193
+ CHARACTER_STYLES = [
194
+ "Detailed/Realistic",
195
+ "Stylized/Expressive",
196
+ "Simple/Clean",
197
+ "Muscular/Athletic",
198
+ "Elegant/Graceful",
199
+ "Cute/Moe",
200
+ "Mature/Adult",
201
+ "Young/Teen",
202
+ "Fantasy/Otherworldly"
203
+ ]
204
+
205
+ LINE_STYLES = [
206
+ "Clean/Precise",
207
+ "Rough/Sketchy",
208
+ "Bold/Thick",
209
+ "Fine/Delicate",
210
+ "Variable Weight",
211
+ "Minimalist",
212
+ "Detailed/Complex"
213
+ ]
214
+
215
+ COMPOSITION_STYLES = [
216
+ "Dynamic/Action",
217
+ "Balanced/Stable",
218
+ "Asymmetrical",
219
+ "Close-up Focus",
220
+ "Wide/Environmental",
221
+ "Dramatic Angles",
222
+ "Traditional/Conservative"
223
+ ]
224
+
225
+ # Optional: Additional utility functions for prompt customization
226
+ def customize_scene_prompt(base_prompt: str, **kwargs) -> str:
227
+ """Allow for dynamic prompt customization if needed."""
228
+ return base_prompt.format(**kwargs)
229
+
230
+ def get_style_modifiers() -> dict:
231
+ """Return common style modifiers that can be added to prompts."""
232
+ return {
233
+ "manga_style": "in traditional manga/anime art style with clean line art and dynamic compositions",
234
+ "dark_theme": "with dark, moody atmosphere and dramatic shadows",
235
+ "action_focus": "emphasizing dynamic action and movement",
236
+ "character_focus": "with detailed character expressions and emotions",
237
+ "cinematic": "with cinematic camera angles and professional composition"
238
+ }
uv.lock ADDED
The diff for this file is too large to render. See raw diff