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(""" """) # Додаємо залежність для відображення/приховування 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()