File size: 24,369 Bytes
26e822e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10a9a4e
b4da720
26e822e
10a9a4e
26e822e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4da720
 
 
 
 
 
 
26e822e
 
 
 
b4da720
 
26e822e
 
 
10a9a4e
b4da720
 
 
 
 
 
 
 
 
 
 
 
 
 
26e822e
 
 
 
 
10a9a4e
b4da720
26e822e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb9ceee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26e822e
 
 
 
 
 
 
 
 
 
 
 
 
 
b4da720
 
 
 
 
 
 
 
26e822e
 
 
b4da720
 
 
26e822e
 
 
b4da720
26e822e
 
 
b4da720
26e822e
8628e0a
 
 
 
 
 
 
 
 
 
 
 
26e822e
8628e0a
b4da720
8628e0a
 
b4da720
8628e0a
b4da720
8628e0a
 
b4da720
 
8628e0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4da720
 
 
 
 
 
 
 
26e822e
b4da720
 
 
 
 
 
 
 
26e822e
 
b4da720
10a9a4e
26e822e
b4da720
 
 
 
 
 
 
26e822e
b4da720
26e822e
fb9ceee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26e822e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb9ceee
 
 
 
 
 
 
 
 
 
b4da720
 
 
 
 
 
 
26e822e
 
5a95cc4
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
import gradio as gr
import os
from pathlib import Path
from datetime import datetime
import matplotlib.pyplot as plt
import logging

logger = logging.getLogger("jira_assistant_interface")

def launch_interface(app):
    """
    Запуск інтерфейсу користувача Gradio
    
    Args:
        app: Екземпляр JiraAssistantApp
    """
    # Функція для обробки завантаження та аналізу CSV
    # Змініть функцію analyze_csv, щоб вона повертала тільки звіт та AI аналіз
    def analyze_csv(file_obj, inactive_days, include_ai, model_type):
        if file_obj is None:
            return "Помилка: файл не вибрано", None
        
        try:
            logger.info(f"Отримано файл: {file_obj.name}, тип: {type(file_obj)}")
            
            # Створення тимчасового файлу
            temp_file_path = os.path.join("temp", f"temp_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv")
            
            # У Gradio 5.19.0 об'єкт файлу має різну структуру
            # file_obj може бути шляхом до файлу або містити атрибут 'name'
            if hasattr(file_obj, 'name'):
                source_path = file_obj.name
                
                # Копіювання файлу
                import shutil
                shutil.copy2(source_path, temp_file_path)
            else:
                # Якщо це не шлях до файлу, ймовірно це вже самі дані
                with open(temp_file_path, "w", encoding="utf-8") as f:
                    f.write(str(file_obj))
            
            # Аналіз даних
            api_key = None
            if include_ai:
                if model_type == "openai":
                    api_key = os.getenv("OPENAI_API_KEY")
                elif model_type == "gemini":
                    api_key = os.getenv("GEMINI_API_KEY")
            
            result = app.analyze_csv_file(
                temp_file_path, 
                inactive_days=inactive_days, 
                include_ai=include_ai,
                api_key=api_key,
                model_type=model_type if include_ai else None
            )
            
            if result.get("error"):
                return result.get("error"), None
            
            # Отримуємо звіт та AI аналіз
            report = result.get("report", "")
            ai_analysis = result.get("ai_analysis", "")
            
            # Якщо AI аналіз не включений або порожній, повертаємо None для ai_output
            if not include_ai or not ai_analysis:
                ai_analysis = None
            
            # Перевіряємо, чи не однакові звіт та AI аналіз
            if ai_analysis == report:
                ai_analysis = "Помилка: AI аналіз ідентичний звіту. Перевірте налаштування LLM."
                    
            return report, ai_analysis
        
        except Exception as e:
            import traceback
            error_msg = f"Помилка аналізу: {str(e)}\n\n{traceback.format_exc()}"
            logger.error(error_msg)
            return error_msg, None

    # Функція для збереження звіту
    def save_report_handler(report_text, format_type, include_visualizations):
        if not report_text:
            return "Помилка: спочатку виконайте аналіз даних"
        
        return app.save_report(
            format_type=format_type,
            include_visualizations=include_visualizations
        )
    
    # Функція для тестування підключення до Jira
    def test_jira_connection_handler(url, username, api_token):
        if not url or not username or not api_token:
            return "Помилка: необхідно заповнити всі поля (URL, користувач, API токен)"
        
        success = app.test_jira_connection(url, username, api_token)
        if success:
            return "✅ Успішне підключення до Jira API"
        else:
            return "❌ Помилка підключення до Jira. Перевірте введені дані."
    
    # Функція для обробки запиту візуалізації
    def on_viz_generate_clicked(viz_type, limit, groupby_text):
        # Конвертація групування в формат API
        groupby_map = {"день": "day", "тиждень": "week", "місяць": "month"}
        groupby = groupby_map.get(groupby_text, "day")
        
        # Якщо немає проаналізованих даних
        if not hasattr(app, 'current_data') or app.current_data is None:
            return gr.Plot.update(value=None), "Спочатку завантажте та проаналізуйте дані"
        
        # Генерація візуалізації
        fig = app.generate_visualization(viz_type, limit=limit, groupby=groupby)
        
        if fig:
            return fig, None
        else:
            return None, f"Не вдалося згенерувати візуалізацію типу '{viz_type}'"
    
    # Функція для збереження конкретної візуалізації
    def save_visualization(viz_type, limit, groupby_text, filename):
        try:
            # Конвертація групування в формат API
            groupby_map = {"день": "day", "тиждень": "week", "місяць": "month"}
            groupby = groupby_map.get(groupby_text, "day")
            
            # Генерація візуалізації для збереження
            fig = app.generate_visualization(viz_type, limit=limit, groupby=groupby)
            
            if fig is None:
                return "Помилка: не вдалося створити візуалізацію"
            
            # Створення імені файлу, якщо не вказано
            if not filename:
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                viz_type_clean = viz_type.lower().replace(' ', '_').replace(':', '_')
                filename = f"viz_{viz_type_clean}_{timestamp}.png"
            
            # Перевірка наявності розширення
            if not any(filename.lower().endswith(ext) for ext in ['.png', '.jpg', '.svg', '.pdf']):
                filename += '.png'
            
            # Створення директорії, якщо не існує
            reports_dir = Path("reports/visualizations")
            reports_dir.mkdir(parents=True, exist_ok=True)
            
            # Шлях до файлу
            filepath = reports_dir / filename
            
            # Збереження візуалізації
            fig.savefig(filepath, dpi=300, bbox_inches='tight')
            plt.close(fig)
            
            return f"✅ Візуалізацію збережено: {filepath}"
        except Exception as e:
            import traceback
            error_msg = f"Помилка збереження візуалізації: {str(e)}\n\n{traceback.format_exc()}"
            logger.error(error_msg)
            return error_msg
    
    # Функція для генерації інфографіки
    def generate_infographic():
        if not hasattr(app, 'current_data') or app.current_data is None:
            return None, "Спочатку завантажте та проаналізуйте дані"
        
        infographic = app.generate_infographic()
        
        if infographic is not None:
            return infographic, "Інфографіку успішно створено"
        else:
            return None, "Помилка: не вдалося створити інфографіку"
    
    # Функція для збереження інфографіки
    def save_infographic(filename):
        try:
            if not hasattr(app, 'current_data') or app.current_data is None:
                return "Помилка: спочатку завантажте та проаналізуйте дані"
            
            # Генерація інфографіки
            infographic = app.generate_infographic()
            
            if infographic is None:
                return "Помилка: не вдалося створити інфографіку"
            
            # Створення імені файлу, якщо не вказано
            if not filename:
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                filename = f"jira_infographic_{timestamp}.png"
            
            # Перевірка наявності розширення
            if not any(filename.lower().endswith(ext) for ext in ['.png', '.jpg', '.svg', '.pdf']):
                filename += '.png'
            
            # Створення директорії, якщо не існує
            reports_dir = Path("reports/infographics")
            reports_dir.mkdir(parents=True, exist_ok=True)
            
            # Шлях до файлу
            filepath = reports_dir / filename
            
            # Збереження інфографіки
            infographic.savefig(filepath, dpi=300, bbox_inches='tight')
            plt.close(infographic)
            
            return f"✅ Інфографіку збережено: {filepath}"
        except Exception as e:
            import traceback
            error_msg = f"Помилка збереження інфографіки: {str(e)}\n\n{traceback.format_exc()}"
            logger.error(error_msg)
            return error_msg
    
    # Створення інтерфейсу Gradio
    with gr.Blocks(title="Jira AI Assistant") as interface:
        gr.Markdown("# 🔍 Jira AI Assistant")
        
        with gr.Tabs():
            with gr.Tab("CSV Аналіз"):
                with gr.Row():
                    with gr.Column(scale=1):
                        file_input = gr.File(label="Завантажити CSV файл Jira")
                        inactive_days = gr.Slider(minimum=1, maximum=90, value=14, step=1, 
                                                label="Кількість днів для визначення неактивних тікетів")
                        
                        include_ai = gr.Checkbox(label="Включити AI аналіз", value=False)
                        
                        # Додаємо вибір моделі для AI аналізу
                        model_type = gr.Radio(
                            choices=["gemini", "openai"],
                            value="gemini",
                            label="Модель для AI аналізу",
                            interactive=True
                        )
                        
                        analyze_btn = gr.Button("Аналізувати", variant="primary")
                        
                        with gr.Accordion("Збереження звіту", open=False):
                            format_type = gr.Radio(
                                choices=["txt", "md", "html", "pdf"],
                                value="txt",
                                label="Формат звіту"
                            )
                            include_visualizations = gr.Checkbox(
                                label="Включити візуалізації",
                                value=True
                            )
                            save_btn = gr.Button("Зберегти звіт")
                            save_status = gr.Textbox(label="Статус збереження")
                    
                    # with gr.Column(scale=2):
                    #     report_output = gr.Textbox(
                    #         label="Звіт аналізу",
                    #         lines=20,
                    #         max_lines=30
                    #     )
                    #     ai_output = gr.Textbox(
                    #         label="AI аналіз",
                    #         lines=20,
                    #         max_lines=30,
                    #         visible=False  # Початково приховано
                    #     )
                    with gr.Column(scale=2):
                        report_output = gr.Markdown(
                            label="Звіт аналізу",
                            value="",
                            elem_id="report_output"
                        )
                        ai_output = gr.Markdown(
                            label="AI аналіз",
                            value="",
                            elem_id="ai_output",
                            visible=False  # Початково приховано
                        )
                        
                        # Додаємо CSS для стилізації Markdown виводу
                        gr.HTML("""
                        <style>
                        #report_output, #ai_output {
                            height: 500px;
                            overflow-y: auto;
                            border: 1px solid #ddd;
                            padding: 10px;
                            border-radius: 4px;
                            background-color: #f9f9f9;
                        }
                        </style>
                        """)

                        

                    # Додаємо залежність для відображення/приховування AI аналізу
                    include_ai.change(
                        lambda x: gr.update(visible=x),
                        inputs=[include_ai],
                        outputs=[ai_output]
                    )

                
                # Функція для оновлення видимості AI аналізу
                def update_ai_output(include_ai, ai_text):
                    if include_ai and ai_text:
                        return gr.update(visible=True, value=ai_text)
                    else:
                        return gr.update(visible=False, value="")

                # Оновлюємо обробник події для аналізу
                analyze_btn.click(
                    analyze_csv,
                    inputs=[file_input, inactive_days, include_ai, model_type],
                    outputs=[report_output, ai_output]
                )

                # Додаємо обробник для відображення/приховування AI аналізу
                analyze_btn.click(
                    update_ai_output,
                    inputs=[include_ai, ai_output],
                    outputs=[ai_output],
                    queue=False  # Виконується одразу після analyze_csv
                )

            
            # Нова вкладка для розширених візуалізацій
            with gr.Tab("Візуалізації"):
                gr.Markdown("## Типи візуалізацій")
                
                with gr.Row():
                    viz_type = gr.Dropdown(
                        choices=[
                            "Статуси", "Пріоритети", "Типи тікетів", "Призначені користувачі",
                            "Активність створення", "Активність оновлення", "Кумулятивне створення",
                            "Неактивні тікети", "Теплова карта: Типи/Статуси", "Часова шкала проекту", "Склад статусів з часом"
                        ],
                        value="Статуси",
                        label="Тип візуалізації"
                    )
                    viz_generate_btn = gr.Button("Генерувати", variant="primary")
                
                # Додаткові параметри для візуалізацій
                with gr.Accordion("Параметри візуалізації", open=False):
                    with gr.Row():
                        viz_param_limit = gr.Slider(minimum=5, maximum=20, value=10, step=1,
                                                 label="Ліміт для топ-візуалізацій")
                        viz_param_groupby = gr.Dropdown(
                            choices=["день", "тиждень", "місяць"],
                            value="день",
                            label="Групування для часових діаграм"
                        )
                
                with gr.Row():
                    viz_plot = gr.Plot(label="Візуалізація")
                    viz_status = gr.Textbox(label="Статус", visible=False)
                
                # Секція збереження візуалізації
                with gr.Row():
                    viz_filename = gr.Textbox(
                        label="Ім'я файлу (опціонально)",
                        placeholder="Залиште порожнім для автоматичного імені"
                    )
                    viz_save_btn = gr.Button("Зберегти візуалізацію", variant="secondary")
                    viz_save_status = gr.Textbox(label="Статус збереження")
                
                # Прив'язуємо обробники подій для візуалізацій
                viz_generate_btn.click(
                    on_viz_generate_clicked,
                    inputs=[viz_type, viz_param_limit, viz_param_groupby],
                    outputs=[viz_plot, viz_status]
                )
                
                viz_save_btn.click(
                    save_visualization,
                    inputs=[viz_type, viz_param_limit, viz_param_groupby, viz_filename],
                    outputs=[viz_save_status]
                )
            
            # Вкладка для інфографіки
            with gr.Tab("Інфографіка"):
                gr.Markdown("## Комплексна інфографіка")
                gr.Markdown("Створює зведену інфографіку з ключовими показниками проекту на основі проаналізованих даних.")
                
                with gr.Row():
                    infographic_generate_btn = gr.Button("Створити інфографіку", variant="primary")
                
                with gr.Row():
                    infographic_plot = gr.Plot(label="Зведена інфографіка")
                    infographic_status = gr.Textbox(label="Статус")
                
                with gr.Row():
                    infographic_filename = gr.Textbox(
                        label="Ім'я файлу (опціонально)",
                        placeholder="Залиште порожнім для автоматичного імені"
                    )
                    infographic_save_btn = gr.Button("Зберегти інфографіку", variant="secondary")
                
                # Прив'язка обробників для інфографіки
                infographic_generate_btn.click(
                    generate_infographic,
                    inputs=[],
                    outputs=[infographic_plot, infographic_status]
                )
                
                infographic_save_btn.click(
                    save_infographic,
                    inputs=[infographic_filename],
                    outputs=[infographic_status]
                )
            
            with gr.Tab("Jira API"):
                gr.Markdown("## Підключення до Jira API")
                
                with gr.Row():
                    jira_url = gr.Textbox(
                        label="Jira URL",
                        placeholder="https://your-company.atlassian.net"
                    )
                    jira_username = gr.Textbox(
                        label="Ім'я користувача Jira",
                        placeholder="email@example.com"
                    )
                    jira_api_token = gr.Textbox(
                        label="Jira API Token",
                        type="password"
                    )
                
                test_connection_btn = gr.Button("Тестувати з'єднання")
                connection_status = gr.Textbox(label="Статус підключення")
                
                test_connection_btn.click(
                    test_jira_connection_handler,
                    inputs=[jira_url, jira_username, jira_api_token],
                    outputs=[connection_status]
                )
                
                gr.Markdown("## ⚠️ Ця функція буде доступна у наступних версіях")
            
            with gr.Tab("AI Асистенти"):
                gr.Markdown("## AI Асистенти для Jira")
                gr.Markdown("⚠️ Ця функція буде доступна у наступних версіях")
                
                with gr.Accordion("Зразок інтерфейсу"):
                    question = gr.Textbox(
                        label="Запитання",
                        placeholder="Наприклад: Які тікети мають найвищий пріоритет?",
                        lines=2
                    )
                    answer = gr.Markdown(label="Відповідь")
            
            with gr.Tab("Інтеграції"):
                gr.Markdown("## Інтеграції з зовнішніми системами")
                gr.Markdown("⚠️ Ця функція буде доступна у наступних версіях")
                
                with gr.Accordion("Slack інтеграція"):
                    slack_channel = gr.Textbox(
                        label="Slack канал",
                        placeholder="#project-updates"
                    )
                    slack_message = gr.Textbox(
                        label="Повідомлення",
                        placeholder="Тижневий звіт по проекту",
                        lines=3
                    )
                    slack_send_btn = gr.Button("Надіслати у Slack", interactive=False)
                
                save_settings_btn = gr.Button("Зберегти налаштування", variant="primary")
                settings_status = gr.Textbox(label="Статус")
                
                # Заглушка для функціоналу налаштувань
                save_settings_btn.click(
                    lambda: "Налаштування збережено. Зміни набудуть чинності після перезапуску програми.",
                    inputs=[],
                    outputs=[settings_status]
                )
            # Додаємо залежність для відображення/приховування вибору моделі
        # Додаємо залежність для відображення/приховування вибору моделі
        include_ai.change(
            lambda x: gr.update(visible=x),
            inputs=[include_ai],
            outputs=[model_type]
        )
    
    # Запуск інтерфейсу
    interface.launch()