jira-ai-assistant-docker / interface.py
DocUA's picture
налаштування AI Аналіз
b4da720
raw
history blame
23.1 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
# Змініть функцію 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 # Початково приховано
)
# Додаємо залежність для відображення/приховування 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()