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