prashanth-kumar-g commited on
Commit
2ac09ab
Β·
verified Β·
1 Parent(s): b6ff102

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +717 -34
src/streamlit_app.py CHANGED
@@ -1,40 +1,723 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
 
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ # ============================
2
+ # Image Caption Studio - Streamlit App
3
+ # ============================
4
+
5
  import streamlit as st
6
+ import torch
7
+ from PIL import Image
8
+ import transformers
9
+ from transformers import BitsAndBytesConfig
10
+ import time
11
+ import warnings
12
+ import random
13
+ from pathlib import Path
14
+ import base64
15
+ import io
16
+ warnings.filterwarnings('ignore')
17
 
18
+ # ============================
19
+ # PAGE CONFIGURATION
20
+ # ============================
21
+
22
+ st.set_page_config(
23
+ page_title="Image Caption Studio",
24
+ page_icon="πŸ–ΌοΈ",
25
+ layout="wide",
26
+ initial_sidebar_state="collapsed"
27
+ )
28
+
29
+ # ============================
30
+ # CUSTOM CSS FOR BEAUTIFUL UI
31
+ # ============================
32
+
33
+ def load_css():
34
+ st.markdown("""
35
+ <style>
36
+ /* Main background */
37
+ .stApp {
38
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
+ }
40
+
41
+ /* Main container */
42
+ .main-container {
43
+ background: rgba(255, 255, 255, 0.95);
44
+ border-radius: 24px;
45
+ padding: 40px;
46
+ margin: 20px;
47
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
48
+ backdrop-filter: blur(10px);
49
+ }
50
+
51
+ /* Title styling */
52
+ .title-container {
53
+ text-align: center;
54
+ margin-bottom: 40px;
55
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
56
+ -webkit-background-clip: text;
57
+ -webkit-text-fill-color: transparent;
58
+ padding: 20px;
59
+ border-radius: 20px;
60
+ }
61
+
62
+ .main-title {
63
+ font-size: 3.5rem !important;
64
+ font-weight: 800 !important;
65
+ margin-bottom: 10px !important;
66
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
67
+ -webkit-background-clip: text;
68
+ -webkit-text-fill-color: transparent;
69
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
70
+ }
71
+
72
+ .subtitle {
73
+ font-size: 1.2rem !important;
74
+ color: #666 !important;
75
+ max-width: 800px;
76
+ margin: 0 auto;
77
+ line-height: 1.6;
78
+ }
79
+
80
+ /* Logo container */
81
+ .logo-container {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ gap: 20px;
86
+ margin-bottom: 20px;
87
+ }
88
+
89
+ .logo-icon {
90
+ font-size: 4rem;
91
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
92
+ -webkit-background-clip: text;
93
+ -webkit-text-fill-color: transparent;
94
+ }
95
+
96
+ /* Image display */
97
+ .image-container {
98
+ border-radius: 20px;
99
+ overflow: hidden;
100
+ box-shadow: 0 10px 30px rgba(0,0,0,0.15);
101
+ margin: 30px auto;
102
+ max-width: 600px;
103
+ border: 3px solid #667eea;
104
+ position: relative;
105
+ }
106
+
107
+ .image-container::before {
108
+ content: '';
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ right: 0;
113
+ bottom: 0;
114
+ background: linear-gradient(45deg, transparent, rgba(102, 126, 234, 0.1));
115
+ z-index: 1;
116
+ }
117
+
118
+ /* Cards */
119
+ .card {
120
+ background: white;
121
+ border-radius: 20px;
122
+ padding: 25px;
123
+ margin: 15px 0;
124
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
125
+ border: 2px solid transparent;
126
+ transition: all 0.3s ease;
127
+ }
128
+
129
+ .card:hover {
130
+ transform: translateY(-5px);
131
+ box-shadow: 0 15px 35px rgba(0,0,0,0.15);
132
+ }
133
+
134
+ .short-card {
135
+ border-color: #4CAF50;
136
+ background: linear-gradient(135deg, #f8fff8 0%, #e8f5e9 100%);
137
+ }
138
+
139
+ .tech-card {
140
+ border-color: #2196F3;
141
+ background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);
142
+ }
143
+
144
+ .human-card {
145
+ border-color: #FF9800;
146
+ background: linear-gradient(135deg, #fff8f8 0%, #fff3e0 100%);
147
+ }
148
+
149
+ /* Badges */
150
+ .badge {
151
+ display: inline-block;
152
+ padding: 8px 16px;
153
+ border-radius: 50px;
154
+ font-weight: 600;
155
+ font-size: 0.9rem;
156
+ margin-bottom: 15px;
157
+ }
158
+
159
+ .short-badge {
160
+ background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
161
+ color: white;
162
+ }
163
+
164
+ .tech-badge {
165
+ background: linear-gradient(135deg, #2196F3 0%, #0D47A1 100%);
166
+ color: white;
167
+ }
168
+
169
+ .human-badge {
170
+ background: linear-gradient(135deg, #FF9800 0%, #EF6C00 100%);
171
+ color: white;
172
+ }
173
+
174
+ /* Buttons */
175
+ .stButton > button {
176
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
177
+ color: white;
178
+ border: none;
179
+ padding: 14px 28px;
180
+ border-radius: 50px;
181
+ font-weight: 600;
182
+ font-size: 1.1rem;
183
+ transition: all 0.3s ease;
184
+ width: 100%;
185
+ margin-top: 20px;
186
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
187
+ }
188
+
189
+ .stButton > button:hover {
190
+ transform: translateY(-2px);
191
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
192
+ }
193
+
194
+ .upload-btn {
195
+ background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%) !important;
196
+ }
197
+
198
+ /* Progress bar */
199
+ .stProgress > div > div > div {
200
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
201
+ }
202
+
203
+ /* Sidebar */
204
+ .css-1d391kg {
205
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
206
+ }
207
+
208
+ /* Success message */
209
+ .success-msg {
210
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
211
+ border: 2px solid #4CAF50;
212
+ border-radius: 15px;
213
+ padding: 20px;
214
+ margin: 20px 0;
215
+ text-align: center;
216
+ font-weight: 600;
217
+ color: #155724;
218
+ }
219
+
220
+ /* Footer */
221
+ .footer {
222
+ text-align: center;
223
+ margin-top: 50px;
224
+ color: #666;
225
+ font-size: 0.9rem;
226
+ padding: 20px;
227
+ border-top: 2px solid #eee;
228
+ }
229
+
230
+ /* Radio buttons */
231
+ .stRadio > div {
232
+ background: white;
233
+ padding: 20px;
234
+ border-radius: 15px;
235
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
236
+ }
237
+
238
+ /* Expander */
239
+ .streamlit-expanderHeader {
240
+ background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%) !important;
241
+ border-radius: 10px !important;
242
+ font-weight: 600 !important;
243
+ }
244
+
245
+ /* Sliders */
246
+ .stSlider {
247
+ padding: 10px 0;
248
+ }
249
+
250
+ /* Word counter */
251
+ .word-counter {
252
+ font-size: 0.9rem;
253
+ color: #666;
254
+ font-style: italic;
255
+ margin-top: 5px;
256
+ }
257
+
258
+ /* Loading animation */
259
+ @keyframes pulse {
260
+ 0% { transform: scale(1); }
261
+ 50% { transform: scale(1.05); }
262
+ 100% { transform: scale(1); }
263
+ }
264
+
265
+ .loading {
266
+ animation: pulse 1.5s infinite;
267
+ }
268
+
269
+ /* Responsive design */
270
+ @media (max-width: 768px) {
271
+ .main-title {
272
+ font-size: 2.5rem !important;
273
+ }
274
+ .main-container {
275
+ padding: 20px;
276
+ margin: 10px;
277
+ }
278
+ }
279
+ </style>
280
+ """, unsafe_allow_html=True)
281
+
282
+ # ============================
283
+ # MODEL LOADING (CACHED)
284
+ # ============================
285
+
286
+ @st.cache_resource(show_spinner=False)
287
+ def load_model():
288
+ """Load the Qwen2.5-VL model with aggressive optimization for free CPU."""
289
+ from transformers import BitsAndBytesConfig
290
+ import torch
291
+
292
+ model_id = "Qwen/Qwen2.5-VL-7B-Instruct"
293
+
294
+ # AGGRESSIVE 4-bit quantization for CPU
295
+ quantization_config = BitsAndBytesConfig(
296
+ load_in_4bit=True,
297
+ bnb_4bit_compute_dtype=torch.float32, # More stable on CPU
298
+ bnb_4bit_quant_type="nf4",
299
+ bnb_4bit_use_double_quant=True,
300
+ )
301
+
302
+ try:
303
+ # KEY CHANGE: Force model to CPU and use memory mapping
304
+ model = transformers.Qwen2_5_VLForConditionalGeneration.from_pretrained(
305
+ model_id,
306
+ quantization_config=quantization_config,
307
+ device_map="cpu", # <-- FORCE CPU
308
+ low_cpu_mem_usage=True, # <-- CRITICAL for low memory
309
+ torch_dtype=torch.float32,
310
+ trust_remote_code=True
311
+ )
312
+
313
+ processor = transformers.Qwen2_5_VLProcessor.from_pretrained(model_id)
314
+ st.success("βœ… Model loaded in 4-bit (Optimized for CPU)")
315
+ return model, processor, "cpu"
316
+
317
+ except Exception as e:
318
+ st.error(f"❌ Model loading failed on CPU: {e}")
319
+ # Fallback: Use a TINY model
320
+ st.info("πŸ”„ Attempting to load a smaller model...")
321
+ return None, None, None
322
 
323
+ # ============================
324
+ # PROMPT TEMPLATES
325
+ # ============================
326
 
327
+ class CaptionPrompts:
328
+ """Class containing prompt templates for different caption styles"""
329
+
330
+ @staticmethod
331
+ def get_short_caption_prompt(word_limit=15):
332
+ """Generate short caption"""
333
+ return f"""<|im_start|>system
334
+ You are an expert image captioning assistant. Generate a VERY SHORT caption describing the image.
335
+ The caption should be concise, under {word_limit} words, and capture the main subject.
336
+ Focus only on the most important elements.<|im_end|>
337
+ <|im_start|>user
338
+ <image>
339
+ Describe this image in a single, very short sentence (under {word_limit} words).<|im_end|>
340
+ <|im_start|>assistant
341
  """
342
+
343
+ @staticmethod
344
+ def get_technical_caption_prompt(word_limit=35):
345
+ """Generate technical caption"""
346
+ return f"""<|im_start|>system
347
+ You are a technical image analysis expert. Generate a detailed technical caption.
348
+ Focus on objective observations, visual characteristics, composition, and technical aspects.
349
+ Use precise terminology. Keep it under {word_limit} words.<|im_end|>
350
+ <|im_start|>user
351
+ <image>
352
+ Provide a technical description of this image with precise observations (under {word_limit} words).<|im_end|>
353
+ <|im_start|>assistant
354
+ """
355
+
356
+ @staticmethod
357
+ def get_human_friendly_caption_prompt(word_limit=25):
358
+ """Generate human-friendly caption"""
359
+ return f"""<|im_start|>system
360
+ You are a friendly storyteller. Generate an engaging, human-friendly caption.
361
+ Make it descriptive, interesting, and easy to understand for general audiences.
362
+ Use vivid language and keep it under {word_limit} words.<|im_end|>
363
+ <|im_start|>user
364
+ <image>
365
+ Create a friendly, engaging caption for this image that tells a story (under {word_limit} words).<|im_end|>
366
+ <|im_start|>assistant
367
+ """
368
+
369
+ # ============================
370
+ # CAPTION GENERATION FUNCTION
371
+ # ============================
372
+
373
+ def generate_caption(model, processor, device, image, prompt: str, max_new_tokens: int = 100) -> str:
374
+ """Generate caption for an image using the given prompt"""
375
+ try:
376
+ # Prepare inputs
377
+ messages = [
378
+ {"role": "user", "content": [
379
+ {"type": "image", "image": image},
380
+ {"type": "text", "text": prompt.split("<|im_start|>user\n")[-1].split("<|im_end|>")[0]}
381
+ ]}
382
+ ]
383
+
384
+ text = processor.apply_chat_template(
385
+ messages,
386
+ tokenize=False,
387
+ add_generation_prompt=True
388
+ )
389
+
390
+ # Prepare image inputs
391
+ image_inputs = processor(text=text, images=image, return_tensors="pt")
392
+ image_inputs = {k: v.to(device) for k, v in image_inputs.items()}
393
+
394
+ # Generate caption
395
+ with torch.no_grad():
396
+ generated_ids = model.generate(
397
+ **image_inputs,
398
+ max_new_tokens=max_new_tokens,
399
+ do_sample=True,
400
+ temperature=0.7,
401
+ top_p=0.9,
402
+ )
403
+
404
+ # Decode the generated text
405
+ generated_ids_trimmed = [
406
+ out_ids[len(in_ids):]
407
+ for in_ids, out_ids in zip(image_inputs["input_ids"], generated_ids)
408
+ ]
409
+
410
+ caption = processor.batch_decode(
411
+ generated_ids_trimmed,
412
+ skip_special_tokens=True,
413
+ clean_up_tokenization_spaces=True
414
+ )[0]
415
+
416
+ return caption.strip()
417
+
418
+ except Exception as e:
419
+ return f"Error generating caption: {str(e)}"
420
+
421
+ # ============================
422
+ # MAIN APP FUNCTION
423
+ # ============================
424
+
425
+ def main():
426
+ # Load custom CSS
427
+ load_css()
428
+
429
+ # Main container
430
+ with st.container():
431
+ st.markdown('<div class="main-container">', unsafe_allow_html=True)
432
+
433
+ # Header with logo and title
434
+ col1, col2, col3 = st.columns([1, 2, 1])
435
+ with col2:
436
+ st.markdown('<div class="logo-container">', unsafe_allow_html=True)
437
+ st.markdown('<span class="logo-icon">πŸ–ΌοΈβœ¨</span>', unsafe_allow_html=True)
438
+ st.markdown('</div>', unsafe_allow_html=True)
439
+
440
+ st.markdown('<h1 class="main-title">Image Caption Studio</h1>', unsafe_allow_html=True)
441
+ st.markdown('<p class="subtitle">Transform your images into beautiful captions using advanced AI. Upload any image and get short, technical, and human-friendly captions instantly!</p>', unsafe_allow_html=True)
442
+
443
+ # Load model (cached)
444
+ model, processor, device = load_model()
445
+
446
+ if model is None:
447
+ st.error("⚠️ Model failed to load. Please refresh the page or check your connection.")
448
+ return
449
+
450
+ # ============================
451
+ # SIDEBAR FOR SETTINGS
452
+ # ============================
453
+
454
+ with st.sidebar:
455
+ st.markdown("## βš™οΈ Settings")
456
+
457
+ # Caption type selection
458
+ caption_type = st.radio(
459
+ "**Select Caption Style:**",
460
+ ["🎯 All Three Styles", "πŸ“ Short Only", "πŸ”¬ Technical Only", "😊 Human-Friendly Only"],
461
+ help="Choose which caption styles to generate"
462
+ )
463
+
464
+ # Advanced options expander
465
+ with st.expander("**βš™οΈ Advanced Options**", expanded=False):
466
+ st.markdown("### Word Limits")
467
+
468
+ short_limit = st.slider(
469
+ "**Short Caption Limit:**",
470
+ min_value=5,
471
+ max_value=25,
472
+ value=15,
473
+ help="Maximum words for short captions"
474
+ )
475
+
476
+ tech_limit = st.slider(
477
+ "**Technical Caption Limit:**",
478
+ min_value=15,
479
+ max_value=50,
480
+ value=35,
481
+ help="Maximum words for technical captions"
482
+ )
483
+
484
+ human_limit = st.slider(
485
+ "**Human-Friendly Limit:**",
486
+ min_value=15,
487
+ max_value=50,
488
+ value=25,
489
+ help="Maximum words for human-friendly captions"
490
+ )
491
+
492
+ # Performance info
493
+ st.markdown("---")
494
+ st.markdown("### πŸ“Š System Info")
495
+ st.info(f"**Device:** {device.upper()}\n\n**Model:** Qwen2.5-VL-7B\n\n**Status:** Ready βœ…")
496
+
497
+ # ============================
498
+ # MAIN CONTENT AREA
499
+ # ============================
500
+
501
+ # Create two columns for layout
502
+ col_left, col_right = st.columns([1, 1])
503
+
504
+ with col_left:
505
+ st.markdown("### πŸ“€ Upload Your Image")
506
+
507
+ # File uploader
508
+ uploaded_file = st.file_uploader(
509
+ "Choose an image...",
510
+ type=['jpg', 'jpeg', 'png', 'bmp', 'tiff'],
511
+ help="Supported formats: JPG, JPEG, PNG, BMP, TIFF"
512
+ )
513
+
514
+ # Display uploaded image
515
+ if uploaded_file is not None:
516
+ try:
517
+ image = Image.open(uploaded_file)
518
+
519
+ # Resize for display
520
+ max_size = (500, 500)
521
+ image.thumbnail(max_size, Image.Resampling.LANCZOS)
522
+
523
+ st.markdown('<div class="image-container">', unsafe_allow_html=True)
524
+ st.image(image, use_column_width=True)
525
+ st.markdown('</div>', unsafe_allow_html=True)
526
+
527
+ # Image info
528
+ st.success(f"βœ… **{uploaded_file.name}** uploaded successfully!")
529
+ st.caption(f"**Size:** {image.size[0]}x{image.size[1]} pixels | **Format:** {image.format}")
530
+
531
+ except Exception as e:
532
+ st.error(f"Error loading image: {str(e)}")
533
+ image = None
534
+ else:
535
+ # Display placeholder
536
+ st.markdown('<div class="image-container">', unsafe_allow_html=True)
537
+ st.image("https://via.placeholder.com/500x300/667eea/ffffff?text=Upload+an+Image",
538
+ use_column_width=True)
539
+ st.markdown('</div>', unsafe_allow_html=True)
540
+ st.info("πŸ‘† Upload an image to get started")
541
+ image = None
542
+
543
+ with col_right:
544
+ st.markdown("### 🎨 Caption Settings")
545
+
546
+ # Display current settings
547
+ if caption_type == "🎯 All Three Styles":
548
+ st.markdown("**Selected:** All caption styles")
549
+ cols = st.columns(3)
550
+ with cols[0]:
551
+ st.markdown('<div class="badge short-badge">Short</div>', unsafe_allow_html=True)
552
+ with cols[1]:
553
+ st.markdown('<div class="badge tech-badge">Technical</div>', unsafe_allow_html=True)
554
+ with cols[2]:
555
+ st.markdown('<div class="badge human-badge">Human-Friendly</div>', unsafe_allow_html=True)
556
+ else:
557
+ st.markdown(f"**Selected:** {caption_type.split(' ')[1]}")
558
+
559
+ # Generate button
560
+ generate_btn = st.button(
561
+ "πŸš€ Generate Captions",
562
+ type="primary",
563
+ disabled=uploaded_file is None,
564
+ use_container_width=True
565
+ )
566
+
567
+ # ============================
568
+ # CAPTION GENERATION
569
+ # ============================
570
+
571
+ if generate_btn and uploaded_file is not None and image is not None:
572
+ try:
573
+ # Progress bar
574
+ progress_bar = st.progress(0)
575
+ status_text = st.empty()
576
+
577
+ # Generate captions based on selection
578
+ captions = {}
579
+
580
+ if caption_type in ["🎯 All Three Styles", "πŸ“ Short Only"]:
581
+ status_text.text("πŸ” Generating short caption...")
582
+ short_prompt = CaptionPrompts.get_short_caption_prompt(short_limit)
583
+ short_caption = generate_caption(model, processor, device, image, short_prompt, 50)
584
+
585
+ # Enforce word limit
586
+ short_words = short_caption.split()
587
+ if len(short_words) > short_limit:
588
+ short_caption = ' '.join(short_words[:short_limit]) + "..."
589
+ captions['short'] = short_caption
590
+ progress_bar.progress(33)
591
+
592
+ if caption_type in ["🎯 All Three Styles", "πŸ”¬ Technical Only"]:
593
+ status_text.text("πŸ”¬ Generating technical caption...")
594
+ tech_prompt = CaptionPrompts.get_technical_caption_prompt(tech_limit)
595
+ tech_caption = generate_caption(model, processor, device, image, tech_prompt, 100)
596
+
597
+ # Enforce word limit
598
+ tech_words = tech_caption.split()
599
+ if len(tech_words) > tech_limit:
600
+ tech_caption = ' '.join(tech_words[:tech_limit]) + "..."
601
+ captions['technical'] = tech_caption
602
+ progress_bar.progress(66 if caption_type == "πŸ”¬ Technical Only" else 66)
603
+
604
+ if caption_type in ["🎯 All Three Styles", "😊 Human-Friendly Only"]:
605
+ status_text.text("😊 Generating human-friendly caption...")
606
+ human_prompt = CaptionPrompts.get_human_friendly_caption_prompt(human_limit)
607
+ human_caption = generate_caption(model, processor, device, image, human_prompt, 100)
608
+
609
+ # Enforce word limit
610
+ human_words = human_caption.split()
611
+ if len(human_words) > human_limit:
612
+ human_caption = ' '.join(human_words[:human_limit]) + "..."
613
+ captions['human'] = human_caption
614
+ progress_bar.progress(100)
615
+
616
+ status_text.text("βœ… Captions generated successfully!")
617
+ time.sleep(0.5)
618
+ progress_bar.empty()
619
+ status_text.empty()
620
+
621
+ # ============================
622
+ # DISPLAY RESULTS
623
+ # ============================
624
+
625
+ st.markdown("---")
626
+ st.markdown("## πŸ“‹ Generated Captions")
627
+
628
+ # Display appropriate cards
629
+ if 'short' in captions:
630
+ st.markdown('<div class="card short-card loading">', unsafe_allow_html=True)
631
+ st.markdown('<div class="badge short-badge">Short Caption</div>', unsafe_allow_html=True)
632
+ st.markdown(f'**{captions["short"]}**')
633
+ st.markdown(f'<div class="word-counter">πŸ“Š Words: {len(captions["short"].split())} / {short_limit}</div>', unsafe_allow_html=True)
634
+ st.markdown('</div>', unsafe_allow_html=True)
635
+
636
+ if 'technical' in captions:
637
+ st.markdown('<div class="card tech-card loading">', unsafe_allow_html=True)
638
+ st.markdown('<div class="badge tech-badge">Technical Caption</div>', unsafe_allow_html=True)
639
+ st.markdown(f'**{captions["technical"]}**')
640
+ st.markdown(f'<div class="word-counter">πŸ“Š Words: {len(captions["technical"].split())} / {tech_limit}</div>', unsafe_allow_html=True)
641
+ st.markdown('</div>', unsafe_allow_html=True)
642
+
643
+ if 'human' in captions:
644
+ st.markdown('<div class="card human-card loading">', unsafe_allow_html=True)
645
+ st.markdown('<div class="badge human-badge">Human-Friendly Caption</div>', unsafe_allow_html=True)
646
+ st.markdown(f'**{captions["human"]}**')
647
+ st.markdown(f'<div class="word-counter">πŸ“Š Words: {len(captions["human"].split())} / {human_limit}</div>', unsafe_allow_html=True)
648
+ st.markdown('</div>', unsafe_allow_html=True)
649
+
650
+ # Copy to clipboard button
651
+ if caption_type == "🎯 All Three Styles":
652
+ all_captions = f"Short: {captions.get('short', '')}\n\nTechnical: {captions.get('technical', '')}\n\nHuman-Friendly: {captions.get('human', '')}"
653
+ else:
654
+ all_captions = list(captions.values())[0]
655
+
656
+ st.download_button(
657
+ label="πŸ’Ύ Download All Captions",
658
+ data=all_captions,
659
+ file_name="captions.txt",
660
+ mime="text/plain",
661
+ use_container_width=True
662
+ )
663
+
664
+ # Success message
665
+ st.markdown('<div class="success-msg">✨ Captions generated successfully! You can copy them or download as text.</div>', unsafe_allow_html=True)
666
+
667
+ except Exception as e:
668
+ st.error(f"❌ Error generating captions: {str(e)}")
669
+
670
+ # ============================
671
+ # FEATURES SECTION
672
+ # ============================
673
+
674
+ st.markdown("---")
675
+ st.markdown("## ✨ Features")
676
+
677
+ features_cols = st.columns(3)
678
+
679
+ with features_cols[0]:
680
+ st.markdown("""
681
+ <div style="text-align: center; padding: 20px;">
682
+ <h3>🎯 Multiple Styles</h3>
683
+ <p>Short, technical, and human-friendly captions tailored to your needs</p>
684
+ </div>
685
+ """, unsafe_allow_html=True)
686
+
687
+ with features_cols[1]:
688
+ st.markdown("""
689
+ <div style="text-align: center; padding: 20px;">
690
+ <h3>⚑ Fast & Accurate</h3>
691
+ <p>Powered by Qwen2.5-VL AI model for precise and quick results</p>
692
+ </div>
693
+ """, unsafe_allow_html=True)
694
+
695
+ with features_cols[2]:
696
+ st.markdown("""
697
+ <div style="text-align: center; padding: 20px;">
698
+ <h3>🎨 Customizable</h3>
699
+ <p>Adjust word limits and choose specific caption styles</p>
700
+ </div>
701
+ """, unsafe_allow_html=True)
702
+
703
+ # ============================
704
+ # FOOTER
705
+ # ============================
706
+
707
+ st.markdown("---")
708
+ st.markdown('<div class="footer">', unsafe_allow_html=True)
709
+ st.markdown("""
710
+ <p>πŸŽ“ <strong>Final Year Project</strong> | MCA Department</p>
711
+ <p>πŸ€– Powered by Qwen2.5-VL AI Model | πŸš€ Built with Streamlit</p>
712
+ <p>πŸ“§ Contact: student@college.edu | πŸ”— GitHub Repository Available</p>
713
+ """, unsafe_allow_html=True)
714
+ st.markdown('</div>', unsafe_allow_html=True)
715
+
716
+ st.markdown('</div>', unsafe_allow_html=True) # Close main container
717
+
718
+ # ============================
719
+ # RUN THE APP
720
+ # ============================
721
 
722
+ if __name__ == "__main__":
723
+ main()