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 def analyze_csv(file_obj, inactive_days, include_ai): if file_obj is None: return "Помилка: файл не вибрано", None, None, None, 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 = os.getenv("OPENAI_API_KEY") if include_ai else None result = app.analyze_csv_file( temp_file_path, inactive_days=inactive_days, include_ai=include_ai, api_key=api_key ) # Видалення тимчасового файлу try: os.remove(temp_file_path) except: pass if result.get("error"): return result.get("error"), None, None, None, None return ( result.get("report", ""), result.get("visualizations", {}).get("status"), result.get("visualizations", {}).get("priority"), result.get("visualizations", {}).get("created_timeline"), result.get("ai_analysis", "AI аналіз буде доступний у наступних версіях додатку.") ) except Exception as e: import traceback error_msg = f"Помилка аналізу: {str(e)}\n\n{traceback.format_exc()}" logger.error(error_msg) return error_msg, None, None, None, 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) analyze_btn = gr.Button("Аналізувати", variant="primary") with gr.Accordion("Збереження звіту", open=False): format_type = gr.Dropdown( choices=["markdown", "html", "pdf"], value="markdown", label="Формат звіту" ) include_visualizations = gr.Checkbox( label="Включити візуалізації", value=True ) save_btn = gr.Button("Зберегти звіт") save_output = gr.Textbox(label="Статус збереження") with gr.Column(scale=2): with gr.Tabs(): with gr.Tab("Звіт"): report_output = gr.Markdown() with gr.Tab("Візуалізації"): with gr.Row(): status_plot = gr.Plot(label="Статуси тікетів") priority_plot = gr.Plot(label="Пріоритети тікетів") timeline_plot = gr.Plot(label="Часова шкала") with gr.Tab("AI Аналіз"): ai_output = gr.Markdown() # Встановлюємо обробники подій analyze_btn.click( analyze_csv, inputs=[file_input, inactive_days, include_ai], outputs=[report_output, status_plot, priority_plot, timeline_plot, ai_output] ) save_btn.click( save_report_handler, inputs=[report_output, format_type, include_visualizations], outputs=[save_output] ) # Нова вкладка для розширених візуалізацій 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) with gr.Tab("Налаштування"): gr.Markdown("## Налаштування програми") with gr.Accordion("Загальні налаштування", open=True): with gr.Row(): theme_dropdown = gr.Dropdown( choices=["Світла", "Темна", "Системна"], value="Системна", label="Тема інтерфейсу" ) language_dropdown = gr.Dropdown( choices=["Українська", "English"], value="Українська", label="Мова інтерфейсу" ) chart_style = gr.Dropdown( choices=["ggplot", "seaborn", "bmh", "classic", "dark_background"], value="ggplot", label="Стиль графіків" ) with gr.Accordion("Налаштування AI", open=True): with gr.Row(): openai_api_key = gr.Textbox( label="OpenAI API ключ", placeholder="sk-...", type="password" ) openai_model = gr.Dropdown( choices=["gpt-3.5-turbo", "gpt-4", "gpt-4o", "gpt-4o-mini"], value="gpt-3.5-turbo", label="Модель OpenAI" ) with gr.Row(): gemini_api_key = gr.Textbox( label="Google Gemini API ключ", placeholder="...", type="password" ) gemini_model = gr.Dropdown( choices=["gemini-pro", "gemini-1.5-pro"], value="gemini-pro", label="Модель Gemini" ) save_settings_btn = gr.Button("Зберегти налаштування", variant="primary") settings_status = gr.Textbox(label="Статус") # Заглушка для функціоналу налаштувань save_settings_btn.click( lambda: "Налаштування збережено. Зміни набудуть чинності після перезапуску програми.", inputs=[], outputs=[settings_status] ) # Запуск інтерфейсу interface.launch() # Можливість запустити інтерфейс самостійно (для тестування) if __name__ == "__main__": import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app import JiraAssistantApp app_instance = JiraAssistantApp() launch_interface(app_instance)