File size: 15,048 Bytes
6a3bd1f
 
f3a4ad9
6a3bd1f
 
 
 
 
f3a4ad9
 
6a3bd1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f3a4ad9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
import gradio as gr
from typing import Dict, List
from style import Style

class UIManager:
    """Manages all UI components and styling for Pixcribe"""

    def __init__(self):
        # Use centralized Style class for all CSS
        self.custom_css = Style.get_all_css()

    def create_header(self):
        """Create application header"""
        return gr.HTML("""
        <div class="app-header">
            <h1 class="app-title">✨ Pixcribe</h1>
            <p class="app-subtitle">AI-Powered Social Media Caption Generator</p>
        </div>
        """)

    def create_info_banner(self):
        """Create informational banner about model loading and processing times"""
        return gr.HTML("""
        <div style="
            background: linear-gradient(135deg, #E8F4F8 0%, #D4E9F2 100%);
            border-left: 4px solid #3498DB;
            border-radius: 16px;
            padding: 24px 32px;
            margin: 0 auto 48px auto;
            max-width: 1200px;
            box-shadow: 0 4px 16px rgba(52, 152, 219, 0.12);
        ">
            <div style="display: flex; align-items: start; gap: 20px;">
                <div style="font-size: 32px; line-height: 1; margin-top: 4px;">⏱️</div>
                <div style="flex: 1;">
                    <h3 style="
                        margin: 0 0 12px 0;
                        font-size: 20px;
                        font-weight: 700;
                        color: #2C3E50;
                        letter-spacing: -0.02em;
                    ">
                        Please Note: Processing Time
                    </h3>
                    <p style="
                        margin: 0 0 12px 0;
                        font-size: 15px;
                        line-height: 1.6;
                        color: #5D6D7E;
                    ">
                        <strong style="color: #2980B9;">Initial setup and model loading may take a while</strong> as multiple AI models
                        are initialized and cached. This includes YOLOv11 object detection, OpenCLIP semantic analysis,
                        Qwen2.5-VL caption generation, and other advanced models.
                    </p>
                    <p style="
                        margin: 0;
                        font-size: 15px;
                        line-height: 1.6;
                        color: #5D6D7E;
                    ">
                        ✨ <strong style="color: #27AE60;">Processing time varies depending on system resources.</strong>
                        Thank you for your patience while we generate high-quality captions!
                    </p>
                </div>
            </div>
        </div>
        """)

    def create_footer(self):
        """Create application footer"""
        return gr.HTML("""
        <div class="app-footer">
            <p class="footer-text">
                Powered by advanced AI models
            </p>
            <p class="footer-models">
                YOLOv11 Β· OpenCLIP ViT-H/14 Β· Qwen2.5-VL-7B Β· EasyOCR Β· Places365 Β· U2-Net
            </p>
            <p class="footer-text" style="margin-top: 32px;">
                Β© 2025 Pixcribe Β· Built for creators
            </p>
        </div>
        """)

    def format_captions_with_copy(self, captions: List[Dict]) -> str:
        """Format captions as HTML with copy functionality"""
        if not captions:
            return "<p style='color: #6C757D; padding: 24px;'>No captions generated</p>"

        captions_html = ""
        for i, cap in enumerate(captions):
            caption_text = cap.get('caption', '')
            hashtags = cap.get('hashtags', [])
            tone = cap.get('tone', 'unknown').title()

            # Create unique ID for each caption
            caption_id = f"caption_{i}"

            # Full text to copy (caption + hashtags)
            full_text = f"{caption_text}\n\n{' '.join([f'#{tag}' for tag in hashtags])}"

            captions_html += f"""
            <div class="caption-card" id="{caption_id}">
                <button class="copy-button" onclick="copyCaption{i}()" id="copy-btn-{i}">
                    πŸ“‹ Copy
                </button>
                <div class="caption-header">Caption {i+1} Β· {tone}</div>
                <div class="caption-text">{caption_text}</div>
                <div class="caption-hashtags">
                    {' '.join([f'#{tag}' for tag in hashtags])}
                </div>
                <textarea id="caption-text-{i}" style="position: absolute; left: -9999px;">{full_text}</textarea>
            </div>

            <script>
            function copyCaption{i}() {{
                const text = document.getElementById('caption-text-{i}').value;
                const btn = document.getElementById('copy-btn-{i}');

                // Try modern clipboard API first
                if (navigator.clipboard && navigator.clipboard.writeText) {{
                    navigator.clipboard.writeText(text).then(() => {{
                        btn.innerHTML = 'βœ“ Copied!';
                        btn.classList.add('copied');
                        setTimeout(() => {{
                            btn.innerHTML = 'πŸ“‹ Copy';
                            btn.classList.remove('copied');
                        }}, 2000);
                    }}).catch(() => {{
                        // Fallback to old method
                        fallbackCopy{i}();
                    }});
                }} else {{
                    // Fallback for older browsers
                    fallbackCopy{i}();
                }}
            }}

            function fallbackCopy{i}() {{
                const textarea = document.getElementById('caption-text-{i}');
                const btn = document.getElementById('copy-btn-{i}');
                textarea.style.position = 'static';
                textarea.style.opacity = '0';
                textarea.select();
                try {{
                    document.execCommand('copy');
                    btn.innerHTML = 'βœ“ Copied!';
                    btn.classList.add('copied');
                    setTimeout(() => {{
                        btn.innerHTML = 'πŸ“‹ Copy';
                        btn.classList.remove('copied');
                    }}, 2000);
                }} catch (err) {{
                    btn.innerHTML = 'βœ— Failed';
                    setTimeout(() => {{
                        btn.innerHTML = 'πŸ“‹ Copy';
                    }}, 2000);
                }}
                textarea.style.position = 'absolute';
                textarea.style.opacity = '1';
            }}
            </script>
            """

        return captions_html

    def create_batch_progress_html(self, current: int, total: int, percent: float, estimated_remaining: int) -> str:
        """
        Create HTML for batch processing progress display.

        Args:
            current: Number of images completed
            total: Total number of images
            percent: Completion percentage (0-100)
            estimated_remaining: Estimated remaining time in seconds

        Returns:
            Formatted HTML string with progress bar and information
        """
        # Convert remaining time to minutes and seconds
        minutes = int(estimated_remaining // 60)
        seconds = int(estimated_remaining % 60)
        time_str = f"{minutes}m {seconds}s" if minutes > 0 else f"{seconds}s"

        html = f"""
        <div class="progress-container">
            <div class="progress-bar-wrapper">
                <div class="progress-bar-fill" style="width: {percent}%;"></div>
            </div>
            <div class="progress-text">
                Processing image {current} of {total}
            </div>
            <div class="progress-stats">
                <span>Progress: {percent:.1f}%</span>
                {f'<span>Estimated time remaining: {time_str}</span>' if estimated_remaining > 0 else ''}
            </div>
        </div>
        """
        return html

    def format_batch_results_html(self, batch_results: Dict) -> str:
        """
        Format batch processing results as HTML.

        Args:
            batch_results: Dictionary containing batch processing results

        Returns:
            Formatted HTML string with all batch results
        """
        results = batch_results.get('results', {})

        if not results:
            return "<p style='color: #6C757D; padding: 24px; text-align: center;'>No results to display</p>"

        # Build summary card
        total_processed = batch_results.get('total_processed', 0)
        total_success = batch_results.get('total_success', 0)
        total_failed = batch_results.get('total_failed', 0)
        total_time = batch_results.get('total_time', 0)
        avg_time = batch_results.get('average_time_per_image', 0)

        html_parts = []

        # Summary card
        html_parts.append(f"""
        <div class="batch-summary-card">
            <div class="summary-title">βœ“ Batch Processing Complete</div>
            <div class="summary-stats">
                <div class="stat-item">
                    <div class="stat-value">{total_processed}</div>
                    <div class="stat-label">Total Processed</div>
                </div>
                <div class="stat-item">
                    <div class="stat-value" style="color: #27AE60;">{total_success}</div>
                    <div class="stat-label">Successful</div>
                </div>
                <div class="stat-item">
                    <div class="stat-value" style="color: #E74C3C;">{total_failed}</div>
                    <div class="stat-label">Failed</div>
                </div>
                <div class="stat-item">
                    <div class="stat-value">{total_time:.1f}s</div>
                    <div class="stat-label">Total Time</div>
                </div>
                <div class="stat-item">
                    <div class="stat-value">{avg_time:.1f}s</div>
                    <div class="stat-label">Avg Per Image</div>
                </div>
            </div>
        </div>
        """)

        # Start results container
        html_parts.append('<div class="batch-results-container">')

        # Process each image result
        for img_idx in sorted(results.keys()):
            img_result = results[img_idx]
            status = img_result.get('status', 'unknown')

            # Determine status icon and color
            if status == 'success':
                status_icon = 'βœ“'
                status_color = '#27AE60'
            else:
                status_icon = 'βœ—'
                status_color = '#E74C3C'

            # Build card
            card_html = f"""
            <details class="batch-result-card" open>
                <summary class="card-header">
                    <span class="card-status" style="color: {status_color};">{status_icon}</span>
                    <span class="card-title">Image {img_idx}</span>
                </summary>
                <div class="card-content">
            """

            if status == 'success':
                result_data = img_result.get('result', {})
                captions = result_data.get('captions', [])

                # Display captions
                for cap in captions:
                    tone = cap.get('tone', 'Unknown').upper()
                    caption_text = cap.get('caption', '')
                    hashtags = cap.get('hashtags', [])

                    card_html += f"""
                    <div class="caption-section">
                        <div class="caption-label">{tone} Style</div>
                        <div class="caption-text">{caption_text}</div>
                        <div class="hashtags-list">
                            {''.join([f'<span class="hashtag-item">#{tag}</span>' for tag in hashtags])}
                        </div>
                    </div>
                    """

                # Display detected objects
                detections = result_data.get('detections', [])
                if detections:
                    object_names = [det.get('class_name', 'unknown') for det in detections]
                    card_html += f"""
                    <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #E9ECEF;">
                        <strong style="color: #495057;">Detected Objects:</strong>
                        <span style="color: #6C757D;"> {', '.join(object_names)}</span>
                    </div>
                    """

                # Display detected brands
                brands = result_data.get('brands', [])
                if brands:
                    brand_names = [
                        brand[0] if isinstance(brand, tuple) else brand
                        for brand in brands
                    ]
                    card_html += f"""
                    <div style="margin-top: 12px;">
                        <strong style="color: #495057;">Detected Brands:</strong>
                        <span style="color: #6C757D;"> {', '.join(brand_names)}</span>
                    </div>
                    """

            else:
                # Display error information
                error = img_result.get('error', {})
                error_type = error.get('type', 'Unknown Error')
                error_message = error.get('message', 'No error message available')

                card_html += f"""
                <div class="error-card-content">
                    <div class="error-title">{error_type}</div>
                    <div class="error-message">{error_message}</div>
                </div>
                """

            card_html += """
                </div>
            </details>
            """

            html_parts.append(card_html)

        # Close results container
        html_parts.append('</div>')

        return ''.join(html_parts)

    def create_export_panel_html(self) -> str:
        """
        Create HTML for export panel with download buttons.

        Returns:
            Formatted HTML string for export panel
        """
        return """
        <div class="export-panel">
            <div class="export-panel-title">πŸ“₯ Export Batch Results</div>
            <div class="export-buttons-row">
                <button class="export-button" id="export-json-btn">
                    <span class="export-button-icon">πŸ“„</span>
                    <span>Download JSON</span>
                </button>
                <button class="export-button" id="export-csv-btn">
                    <span class="export-button-icon">πŸ“Š</span>
                    <span>Download CSV</span>
                </button>
                <button class="export-button" id="export-zip-btn">
                    <span class="export-button-icon">πŸ“¦</span>
                    <span>Download ZIP</span>
                </button>
            </div>
        </div>
        """


print("βœ“ UIManager (V5 with Batch Processing Support) defined")