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 | |
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) |