K00B404 commited on
Commit
ea21229
·
verified ·
1 Parent(s): 135c444

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +428 -0
app.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: prompt_builder.py
2
+ import os
3
+ from collections import defaultdict
4
+ from pathlib import Path
5
+ import logging
6
+ import json
7
+ import configparser
8
+
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
+ handlers=[logging.StreamHandler()]
14
+ )
15
+ logger = logging.getLogger("PromptBuilder")
16
+
17
+ # Optional imports with better error handling
18
+ try:
19
+ from llama_cpp import Llama
20
+ LLAMA_AVAILABLE = True
21
+ except ImportError:
22
+ logger.warning("llama-cpp-python not installed. LLM enhancement will be disabled.")
23
+ LLAMA_AVAILABLE = False
24
+
25
+ try:
26
+ import gradio as gr
27
+ GRADIO_AVAILABLE = True
28
+ except ImportError:
29
+ logger.error("Gradio not installed. Cannot launch UI.")
30
+ GRADIO_AVAILABLE = False
31
+
32
+
33
+ class PromptBuilder:
34
+ def __init__(self, options_dir="prompt_options", config_file="config.ini"):
35
+ self.options_dir = options_dir
36
+ self.config = self._load_config(config_file)
37
+ self.options = self._load_options()
38
+ self.llm = None
39
+
40
+ # Initialize LLM if available and configured
41
+ if LLAMA_AVAILABLE and self.config.get('llm', 'enabled') == 'True':
42
+ self._initialize_llm()
43
+
44
+ def _load_config(self, config_file):
45
+ """Load configuration from config.ini file or create with defaults"""
46
+ config = configparser.ConfigParser()
47
+
48
+ # Default configuration
49
+ config['general'] = {
50
+ 'options_dir': 'prompt_options',
51
+ 'history_file': 'prompt_history.json'
52
+ }
53
+
54
+ config['llm'] = {
55
+ 'enabled': 'True',
56
+ 'model_path': 'models/model.gguf',
57
+ 'system_prompt': 'You are a visual prompt engineer and expert in visual descriptions influenced by keywords.',
58
+ 'n_gpu_layers': '30',
59
+ 'seed': '1337',
60
+ 'context_size': '2048'
61
+ }
62
+
63
+ # Try to load existing config
64
+ if os.path.exists(config_file):
65
+ try:
66
+ config.read(config_file)
67
+ logger.info(f"Loaded configuration from {config_file}")
68
+ except Exception as e:
69
+ logger.error(f"Error loading config: {e}")
70
+ else:
71
+ # Save default config
72
+ try:
73
+ os.makedirs(os.path.dirname(config_file), exist_ok=True)
74
+ with open(config_file, 'w') as f:
75
+ config.write(f)
76
+ logger.info(f"Created default configuration at {config_file}")
77
+ except Exception as e:
78
+ logger.error(f"Error creating config: {e}")
79
+
80
+ return config
81
+
82
+ def _initialize_llm(self):
83
+ """Initialize the LLM with configuration parameters"""
84
+ try:
85
+ model_path = self.config.get('llm', 'model_path')
86
+ if not os.path.exists(model_path):
87
+ logger.error(f"Model file not found: {model_path}")
88
+ return
89
+
90
+ self.llm = Llama(
91
+ model_path=model_path,
92
+ n_gpu_layers=self.config.getint('llm', 'n_gpu_layers'),
93
+ seed=self.config.getint('llm', 'seed'),
94
+ n_ctx=self.config.getint('llm', 'context_size'),
95
+ )
96
+ logger.info(f"LLM initialized successfully with model: {model_path}")
97
+ except Exception as e:
98
+ logger.error(f"Failed to initialize LLM: {e}")
99
+ self.llm = None
100
+
101
+ def _ensure_directory_exists(self, directory):
102
+ """Ensure the specified directory exists"""
103
+ Path(directory).mkdir(parents=True, exist_ok=True)
104
+
105
+ def _load_options(self):
106
+ """Load prompt options from text files"""
107
+ options = defaultdict(dict)
108
+
109
+ # Ensure options directory exists
110
+ self._ensure_directory_exists(self.options_dir)
111
+
112
+ try:
113
+ for filename in os.listdir(self.options_dir):
114
+ if filename.endswith(".txt"):
115
+ path = os.path.join(self.options_dir, filename)
116
+ key = filename.replace(".txt", "")
117
+
118
+ try:
119
+ if '.' in key:
120
+ group, field = key.split('.', 1)
121
+ with open(path, "r", encoding="utf-8") as f:
122
+ options[group][field] = [line.strip() for line in f if line.strip()]
123
+ else:
124
+ # top-level group
125
+ with open(path, "r", encoding="utf-8") as f:
126
+ options["general"][key] = [line.strip() for line in f if line.strip()]
127
+ except Exception as e:
128
+ logger.error(f"Error loading options from {path}: {e}")
129
+ except Exception as e:
130
+ logger.error(f"Error accessing options directory: {e}")
131
+
132
+ return options
133
+
134
+ def get_choices(self, group, field):
135
+ """Get choices for a specific group and field"""
136
+ return self.options.get(group, {}).get(field, [])
137
+
138
+ def build_prompt(self, base_prompt="", custom_tags=None, enhance=False, **field_values):
139
+ """Build a prompt from selected options"""
140
+ parts = [base_prompt] if base_prompt else []
141
+
142
+ # Add field values to the prompt parts
143
+ for key, value in field_values.items():
144
+ if not value:
145
+ continue
146
+
147
+ if key in ("styles", "lighting", "mood"):
148
+ if key == "styles":
149
+ parts.append(f"in {value} style")
150
+ elif key == "lighting":
151
+ parts.append(f"with {value} lighting")
152
+ elif key == "mood":
153
+ parts.append(f"evoking a {value} mood")
154
+ else:
155
+ parts.append(value)
156
+
157
+ # Add custom tags
158
+ if custom_tags:
159
+ parts.append(custom_tags)
160
+
161
+ # Join parts into a basic prompt
162
+ basic_prompt = ", ".join(filter(None, parts))
163
+
164
+ # Enhance the prompt if requested and LLM is available
165
+ if enhance and self.llm is not None:
166
+ try:
167
+ return self.enhance_prompt(basic_prompt)
168
+ except Exception as e:
169
+ logger.error(f"Error enhancing prompt: {e}")
170
+ return f"Error enhancing prompt: {e}\nBasic prompt: {basic_prompt}"
171
+ else:
172
+ return basic_prompt
173
+
174
+ return "No prompt built!"
175
+
176
+ def enhance_prompt(self, prompt: str) -> str:
177
+ """Enhance a prompt using the LLM"""
178
+ if not self.llm:
179
+ return f"LLM not available. Basic prompt: {prompt}"
180
+
181
+ try:
182
+ system_prompt = self.config.get('llm', 'system_prompt')
183
+
184
+ base_prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
185
+
186
+ {system_prompt}<|eot_id|><|start_header_id|>user<|end_header_id|>
187
+
188
+ {prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
189
+
190
+ """
191
+
192
+ output = self.llm(
193
+ base_prompt,
194
+ max_tokens=256, # Generate more tokens for better descriptions
195
+ stop=["<|eot_id|>"],
196
+ echo=False,
197
+ )
198
+
199
+ if output and "choices" in output and output["choices"]:
200
+ return output["choices"][0]["text"].strip()
201
+ else:
202
+ logger.warning("LLM returned empty or invalid response")
203
+ return prompt
204
+ except Exception as e:
205
+ logger.error(f"Error in LLM inference: {e}")
206
+ return f"Error enhancing: {e}\nOriginal prompt: {prompt}"
207
+
208
+ def save_prompt_to_history(self, prompt):
209
+ """Save generated prompt to history file"""
210
+ history_file = self.config.get('general', 'history_file')
211
+ try:
212
+ history = []
213
+ if os.path.exists(history_file):
214
+ with open(history_file, 'r', encoding='utf-8') as f:
215
+ history = json.load(f)
216
+
217
+ # Add new prompt with timestamp
218
+ from datetime import datetime
219
+ history.append({
220
+ "prompt": prompt,
221
+ "timestamp": datetime.now().isoformat()
222
+ })
223
+
224
+ # Keep only last 100 prompts
225
+ history = history[-100:]
226
+
227
+ with open(history_file, 'w', encoding='utf-8') as f:
228
+ json.dump(history, f, indent=2)
229
+
230
+ logger.info(f"Saved prompt to history: {prompt[:30]}...")
231
+ except Exception as e:
232
+ logger.error(f"Error saving prompt to history: {e}")
233
+
234
+
235
+ def create_ui(builder):
236
+ """Create the Gradio UI"""
237
+ with gr.Blocks(title="Prompt Builder UI") as demo:
238
+ gr.Markdown("# 🌟 Prompt Builder\nCompose generative prompts using curated options.")
239
+
240
+ with gr.Row():
241
+ with gr.Column(scale=3):
242
+ base_prompt = gr.Textbox(
243
+ label="Base Prompt",
244
+ placeholder="e.g., A portrait of...",
245
+ lines=2
246
+ )
247
+ with gr.Column(scale=1):
248
+ llama_prompt_enhancing = gr.Checkbox(
249
+ label="Enhance with LLM",
250
+ value=builder.llm is not None,
251
+ interactive=builder.llm is not None,
252
+ info="Enhance prompt with LLM" if builder.llm is not None else "LLM not available"
253
+ )
254
+
255
+ custom_tags = gr.Textbox(
256
+ label="Custom Tags",
257
+ placeholder="e.g., cinematic, trending on ArtStation, detailed, 8k"
258
+ )
259
+
260
+ with gr.Tabs():
261
+ with gr.TabItem("Character"):
262
+ with gr.Row():
263
+ with gr.Column():
264
+ gender = gr.Dropdown(
265
+ choices=builder.get_choices("character", "gender"),
266
+ label="Gender"
267
+ )
268
+ body = gr.Dropdown(
269
+ choices=builder.get_choices("character", "body"),
270
+ label="Body"
271
+ )
272
+ clothing = gr.Dropdown(
273
+ choices=builder.get_choices("character", "clothing"),
274
+ label="Clothing"
275
+ )
276
+ with gr.Column():
277
+ hair = gr.Dropdown(
278
+ choices=builder.get_choices("character", "hair"),
279
+ label="Hair"
280
+ )
281
+ eyes = gr.Dropdown(
282
+ choices=builder.get_choices("character", "eyes"),
283
+ label="Eyes"
284
+ )
285
+
286
+ with gr.TabItem("Background"):
287
+ with gr.Row():
288
+ with gr.Column():
289
+ land_type = gr.Dropdown(
290
+ choices=builder.get_choices("background", "land_type"),
291
+ label="Land Type"
292
+ )
293
+ sky = gr.Dropdown(
294
+ choices=builder.get_choices("background", "sky"),
295
+ label="Sky"
296
+ )
297
+ with gr.Column():
298
+ flora = gr.Dropdown(
299
+ choices=builder.get_choices("background", "flora"),
300
+ label="Flora"
301
+ )
302
+ fauna = gr.Dropdown(
303
+ choices=builder.get_choices("background", "fauna"),
304
+ label="Fauna"
305
+ )
306
+
307
+ with gr.TabItem("Style"):
308
+ with gr.Row():
309
+ with gr.Column():
310
+ styles = gr.Dropdown(
311
+ choices=builder.get_choices("general", "styles"),
312
+ label="Style"
313
+ )
314
+ with gr.Column():
315
+ lighting = gr.Dropdown(
316
+ choices=builder.get_choices("general", "lighting"),
317
+ label="Lighting"
318
+ )
319
+ mood = gr.Dropdown(
320
+ choices=builder.get_choices("general", "mood"),
321
+ label="Mood"
322
+ )
323
+
324
+ with gr.Row():
325
+ with gr.Column(scale=4):
326
+ output = gr.Textbox(
327
+ label="Generated Prompt",
328
+ lines=4
329
+ )
330
+ with gr.Column(scale=1):
331
+ copy_btn = gr.Button("📋 Copy to Clipboard")
332
+ save_btn = gr.Button("💾 Save to History")
333
+ clear_btn = gr.Button("🧹 Clear All")
334
+
335
+ with gr.Row():
336
+ generate_btn = gr.Button("🔮 Build Prompt", variant="primary", size="lg")
337
+
338
+ # Handle events
339
+ def generate_prompt_handler(
340
+ base_prompt, custom_tags, gender, body, clothing, hair, eyes,
341
+ land_type, sky, flora, fauna, styles, lighting, mood, llama_prompt_enhancing
342
+ ):
343
+ result = builder.build_prompt(
344
+ base_prompt=base_prompt,
345
+ enhance=llama_prompt_enhancing,
346
+ custom_tags=custom_tags,
347
+ gender=gender,
348
+ body=body,
349
+ clothing=clothing,
350
+ hair=hair,
351
+ eyes=eyes,
352
+ land_type=land_type,
353
+ sky=sky,
354
+ flora=flora,
355
+ fauna=fauna,
356
+ styles=styles,
357
+ lighting=lighting,
358
+ mood=mood
359
+ )
360
+ return result
361
+
362
+ def save_to_history(prompt):
363
+ if prompt:
364
+ builder.save_prompt_to_history(prompt)
365
+ return gr.update(value="Saved to history!")
366
+ return gr.update(value="Nothing to save")
367
+
368
+ def clear_all():
369
+ return "", "", None, None, None, None, None, None, None, None, None, None, None, None, False, ""
370
+
371
+ # Connect event handlers
372
+ generate_btn.click(
373
+ fn=generate_prompt_handler,
374
+ inputs=[
375
+ base_prompt, custom_tags, gender, body, clothing, hair, eyes,
376
+ land_type, sky, flora, fauna, styles, lighting, mood, llama_prompt_enhancing
377
+ ],
378
+ outputs=[output]
379
+ )
380
+
381
+ save_btn.click(
382
+ fn=save_to_history,
383
+ inputs=[output],
384
+ outputs=[output]
385
+ )
386
+
387
+ clear_btn.click(
388
+ fn=clear_all,
389
+ inputs=[],
390
+ outputs=[
391
+ base_prompt, custom_tags, gender, body, clothing, hair, eyes,
392
+ land_type, sky, flora, fauna, styles, lighting, mood, llama_prompt_enhancing, output
393
+ ]
394
+ )
395
+
396
+ # JavaScript for copy to clipboard function
397
+ copy_btn.click(
398
+ None,
399
+ _js="""
400
+ () => {
401
+ const output = document.querySelector('#output textarea');
402
+ if (output) {
403
+ navigator.clipboard.writeText(output.value);
404
+ return "Copied to clipboard!";
405
+ }
406
+ return "Nothing to copy";
407
+ }
408
+ """,
409
+ outputs=[output]
410
+ )
411
+
412
+ return demo
413
+
414
+
415
+ def main():
416
+ """Main entry point for the application"""
417
+ builder = PromptBuilder()
418
+
419
+ if not GRADIO_AVAILABLE:
420
+ logger.error("Cannot start UI - Gradio not available")
421
+ return
422
+
423
+ demo = create_ui(builder)
424
+ demo.launch()
425
+
426
+
427
+ if __name__ == "__main__":
428
+ main()