Spaces:
Runtime error
Runtime error
| 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() |