jira-ai-assistant-docker / interface.py
DocUA's picture
debug=True
5a95cc4
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()