jira-ai-assistant-docker / interface.py
DocUA's picture
Покращена візуалізація рапортів
fb9ceee
raw
history blame
23.9 kB
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)