Spaces:
Runtime error
Runtime error
налаштування AI Аналіз
Browse files- app.py +2 -1
- interface.py +82 -90
- modules/ai_analysis/llm_connector.py +85 -51
- requirements.txt +1 -1
app.py
CHANGED
@@ -325,4 +325,5 @@ app = JiraAssistantApp()
|
|
325 |
# Точка входу для запуску з командного рядка
|
326 |
if __name__ == "__main__":
|
327 |
from interface import launch_interface
|
328 |
-
launch_interface(app)
|
|
|
|
325 |
# Точка входу для запуску з командного рядка
|
326 |
if __name__ == "__main__":
|
327 |
from interface import launch_interface
|
328 |
+
interface = launch_interface(app)
|
329 |
+
interface.launch()
|
interface.py
CHANGED
@@ -16,7 +16,7 @@ def launch_interface(app):
|
|
16 |
"""
|
17 |
# Функція для обробки завантаження та аналізу CSV
|
18 |
# Змініть функцію analyze_csv, щоб вона повертала тільки звіт та AI аналіз
|
19 |
-
def analyze_csv(file_obj, inactive_days, include_ai):
|
20 |
if file_obj is None:
|
21 |
return "Помилка: файл не вибрано", None
|
22 |
|
@@ -40,34 +40,44 @@ def launch_interface(app):
|
|
40 |
f.write(str(file_obj))
|
41 |
|
42 |
# Аналіз даних
|
43 |
-
api_key =
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
result = app.analyze_csv_file(
|
45 |
temp_file_path,
|
46 |
inactive_days=inactive_days,
|
47 |
include_ai=include_ai,
|
48 |
-
api_key=api_key
|
|
|
49 |
)
|
50 |
|
51 |
-
# Видалення тимчасового файлу
|
52 |
-
try:
|
53 |
-
os.remove(temp_file_path)
|
54 |
-
except:
|
55 |
-
pass
|
56 |
-
|
57 |
if result.get("error"):
|
58 |
return result.get("error"), None
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
except Exception as e:
|
66 |
import traceback
|
67 |
error_msg = f"Помилка аналізу: {str(e)}\n\n{traceback.format_exc()}"
|
68 |
logger.error(error_msg)
|
69 |
return error_msg, None
|
70 |
-
|
71 |
# Функція для збереження звіту
|
72 |
def save_report_handler(report_text, format_type, include_visualizations):
|
73 |
if not report_text:
|
@@ -213,41 +223,72 @@ def launch_interface(app):
|
|
213 |
|
214 |
include_ai = gr.Checkbox(label="Включити AI аналіз", value=False)
|
215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
analyze_btn = gr.Button("Аналізувати", variant="primary")
|
217 |
|
218 |
with gr.Accordion("Збереження звіту", open=False):
|
219 |
-
format_type = gr.
|
220 |
-
choices=["
|
221 |
-
value="
|
222 |
label="Формат звіту"
|
223 |
)
|
224 |
include_visualizations = gr.Checkbox(
|
225 |
-
label="Включити візуалізації",
|
226 |
value=True
|
227 |
)
|
228 |
save_btn = gr.Button("Зберегти звіт")
|
229 |
-
|
230 |
|
231 |
with gr.Column(scale=2):
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
|
239 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
analyze_btn.click(
|
241 |
analyze_csv,
|
242 |
-
inputs=[file_input, inactive_days, include_ai],
|
243 |
outputs=[report_output, ai_output]
|
244 |
)
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
|
|
|
|
250 |
)
|
|
|
251 |
|
252 |
# Нова вкладка для розширених візуалізацій
|
253 |
with gr.Tab("Візуалізації"):
|
@@ -389,53 +430,6 @@ def launch_interface(app):
|
|
389 |
lines=3
|
390 |
)
|
391 |
slack_send_btn = gr.Button("Надіслати у Slack", interactive=False)
|
392 |
-
|
393 |
-
with gr.Tab("Налаштування"):
|
394 |
-
gr.Markdown("## Налаштування програми")
|
395 |
-
|
396 |
-
with gr.Accordion("Загальні налаштування", open=True):
|
397 |
-
with gr.Row():
|
398 |
-
theme_dropdown = gr.Dropdown(
|
399 |
-
choices=["Світла", "Темна", "Системна"],
|
400 |
-
value="Системна",
|
401 |
-
label="Тема інтерфейсу"
|
402 |
-
)
|
403 |
-
language_dropdown = gr.Dropdown(
|
404 |
-
choices=["Українська", "English"],
|
405 |
-
value="Українська",
|
406 |
-
label="Мова інтерфейсу"
|
407 |
-
)
|
408 |
-
|
409 |
-
chart_style = gr.Dropdown(
|
410 |
-
choices=["ggplot", "seaborn", "bmh", "classic", "dark_background"],
|
411 |
-
value="ggplot",
|
412 |
-
label="Стиль графіків"
|
413 |
-
)
|
414 |
-
|
415 |
-
with gr.Accordion("Налаштування AI", open=True):
|
416 |
-
with gr.Row():
|
417 |
-
openai_api_key = gr.Textbox(
|
418 |
-
label="OpenAI API ключ",
|
419 |
-
placeholder="sk-...",
|
420 |
-
type="password"
|
421 |
-
)
|
422 |
-
openai_model = gr.Dropdown(
|
423 |
-
choices=["gpt-3.5-turbo", "gpt-4", "gpt-4o", "gpt-4o-mini"],
|
424 |
-
value="gpt-3.5-turbo",
|
425 |
-
label="Модель OpenAI"
|
426 |
-
)
|
427 |
-
|
428 |
-
with gr.Row():
|
429 |
-
gemini_api_key = gr.Textbox(
|
430 |
-
label="Google Gemini API ключ",
|
431 |
-
placeholder="...",
|
432 |
-
type="password"
|
433 |
-
)
|
434 |
-
gemini_model = gr.Dropdown(
|
435 |
-
choices=["gemini-pro", "gemini-1.5-pro"],
|
436 |
-
value="gemini-pro",
|
437 |
-
label="Модель Gemini"
|
438 |
-
)
|
439 |
|
440 |
save_settings_btn = gr.Button("Зберегти налаштування", variant="primary")
|
441 |
settings_status = gr.Textbox(label="Статус")
|
@@ -446,15 +440,13 @@ def launch_interface(app):
|
|
446 |
inputs=[],
|
447 |
outputs=[settings_status]
|
448 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
449 |
|
450 |
# Запуск інтерфейсу
|
451 |
-
interface.launch()
|
452 |
-
|
453 |
-
# Можливість запустити інтерфейс самостійно (для тестування)
|
454 |
-
if __name__ == "__main__":
|
455 |
-
import sys
|
456 |
-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
457 |
-
|
458 |
-
from app import JiraAssistantApp
|
459 |
-
app_instance = JiraAssistantApp()
|
460 |
-
launch_interface(app_instance)
|
|
|
16 |
"""
|
17 |
# Функція для обробки завантаження та аналізу CSV
|
18 |
# Змініть функцію analyze_csv, щоб вона повертала тільки звіт та AI аналіз
|
19 |
+
def analyze_csv(file_obj, inactive_days, include_ai, model_type):
|
20 |
if file_obj is None:
|
21 |
return "Помилка: файл не вибрано", None
|
22 |
|
|
|
40 |
f.write(str(file_obj))
|
41 |
|
42 |
# Аналіз даних
|
43 |
+
api_key = None
|
44 |
+
if include_ai:
|
45 |
+
if model_type == "openai":
|
46 |
+
api_key = os.getenv("OPENAI_API_KEY")
|
47 |
+
elif model_type == "gemini":
|
48 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
49 |
+
|
50 |
result = app.analyze_csv_file(
|
51 |
temp_file_path,
|
52 |
inactive_days=inactive_days,
|
53 |
include_ai=include_ai,
|
54 |
+
api_key=api_key,
|
55 |
+
model_type=model_type if include_ai else None
|
56 |
)
|
57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
if result.get("error"):
|
59 |
return result.get("error"), None
|
60 |
+
|
61 |
+
# Отримуємо звіт та AI аналіз
|
62 |
+
report = result.get("report", "")
|
63 |
+
ai_analysis = result.get("ai_analysis", "")
|
64 |
+
|
65 |
+
# Якщо AI аналіз не включений або порожній, повертаємо None для ai_output
|
66 |
+
if not include_ai or not ai_analysis:
|
67 |
+
ai_analysis = None
|
68 |
+
|
69 |
+
# Перевіряємо, чи не однакові звіт та AI аналіз
|
70 |
+
if ai_analysis == report:
|
71 |
+
ai_analysis = "Помилка: AI аналіз ідентичний звіту. Перевірте налаштування LLM."
|
72 |
+
|
73 |
+
return report, ai_analysis
|
74 |
|
75 |
except Exception as e:
|
76 |
import traceback
|
77 |
error_msg = f"Помилка аналізу: {str(e)}\n\n{traceback.format_exc()}"
|
78 |
logger.error(error_msg)
|
79 |
return error_msg, None
|
80 |
+
|
81 |
# Функція для збереження звіту
|
82 |
def save_report_handler(report_text, format_type, include_visualizations):
|
83 |
if not report_text:
|
|
|
223 |
|
224 |
include_ai = gr.Checkbox(label="Включити AI аналіз", value=False)
|
225 |
|
226 |
+
# Додаємо вибір моделі для AI аналізу
|
227 |
+
model_type = gr.Radio(
|
228 |
+
choices=["gemini", "openai"],
|
229 |
+
value="gemini",
|
230 |
+
label="Модель для AI аналізу",
|
231 |
+
interactive=True
|
232 |
+
)
|
233 |
+
|
234 |
analyze_btn = gr.Button("Аналізувати", variant="primary")
|
235 |
|
236 |
with gr.Accordion("Збереження звіту", open=False):
|
237 |
+
format_type = gr.Radio(
|
238 |
+
choices=["txt", "md", "html", "pdf"],
|
239 |
+
value="txt",
|
240 |
label="Формат звіту"
|
241 |
)
|
242 |
include_visualizations = gr.Checkbox(
|
243 |
+
label="Включити візуалізації",
|
244 |
value=True
|
245 |
)
|
246 |
save_btn = gr.Button("Зберегти звіт")
|
247 |
+
save_status = gr.Textbox(label="Статус збереження")
|
248 |
|
249 |
with gr.Column(scale=2):
|
250 |
+
report_output = gr.Textbox(
|
251 |
+
label="Звіт аналізу",
|
252 |
+
lines=20,
|
253 |
+
max_lines=30
|
254 |
+
)
|
255 |
+
ai_output = gr.Textbox(
|
256 |
+
label="AI аналіз",
|
257 |
+
lines=20,
|
258 |
+
max_lines=30,
|
259 |
+
visible=False # Початково приховано
|
260 |
+
)
|
261 |
+
|
262 |
+
# Додаємо залежність для відображення/приховування AI аналізу
|
263 |
+
include_ai.change(
|
264 |
+
lambda x: gr.update(visible=x),
|
265 |
+
inputs=[include_ai],
|
266 |
+
outputs=[ai_output]
|
267 |
+
)
|
268 |
+
|
269 |
|
270 |
+
# Функція для оновлення видимості AI аналізу
|
271 |
+
def update_ai_output(include_ai, ai_text):
|
272 |
+
if include_ai and ai_text:
|
273 |
+
return gr.update(visible=True, value=ai_text)
|
274 |
+
else:
|
275 |
+
return gr.update(visible=False, value="")
|
276 |
+
|
277 |
+
# Оновлюємо обробник події для аналізу
|
278 |
analyze_btn.click(
|
279 |
analyze_csv,
|
280 |
+
inputs=[file_input, inactive_days, include_ai, model_type],
|
281 |
outputs=[report_output, ai_output]
|
282 |
)
|
283 |
+
|
284 |
+
# Додаємо обробник для відображення/приховування AI аналізу
|
285 |
+
analyze_btn.click(
|
286 |
+
update_ai_output,
|
287 |
+
inputs=[include_ai, ai_output],
|
288 |
+
outputs=[ai_output],
|
289 |
+
queue=False # Виконується одразу після analyze_csv
|
290 |
)
|
291 |
+
|
292 |
|
293 |
# Нова вкладка для розширених візуалізацій
|
294 |
with gr.Tab("Візуалізації"):
|
|
|
430 |
lines=3
|
431 |
)
|
432 |
slack_send_btn = gr.Button("Надіслати у Slack", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
433 |
|
434 |
save_settings_btn = gr.Button("Зберегти налаштування", variant="primary")
|
435 |
settings_status = gr.Textbox(label="Статус")
|
|
|
440 |
inputs=[],
|
441 |
outputs=[settings_status]
|
442 |
)
|
443 |
+
# Додаємо залежність для відображення/приховування вибору моделі
|
444 |
+
# Додаємо залежність для відображення/приховування вибору моделі
|
445 |
+
include_ai.change(
|
446 |
+
lambda x: gr.update(visible=x),
|
447 |
+
inputs=[include_ai],
|
448 |
+
outputs=[model_type]
|
449 |
+
)
|
450 |
|
451 |
# Запуск інтерфейсу
|
452 |
+
interface.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/ai_analysis/llm_connector.py
CHANGED
@@ -3,7 +3,8 @@ import json
|
|
3 |
import logging
|
4 |
import requests
|
5 |
from typing import Dict, List, Any, Optional
|
6 |
-
|
|
|
7 |
from datetime import datetime, timedelta
|
8 |
|
9 |
logger = logging.getLogger(__name__)
|
@@ -201,60 +202,93 @@ class LLMConnector:
|
|
201 |
if not self.api_key:
|
202 |
return "Не вказано API ключ Gemini"
|
203 |
|
204 |
-
#
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
"
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
|
215 |
-
для покращення процесу. Будьте конкретними та орієнтованими на дії.
|
216 |
-
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
|
217 |
-
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""
|
218 |
-
}
|
219 |
-
]
|
220 |
-
},
|
221 |
-
{
|
222 |
-
"parts": [
|
223 |
-
{
|
224 |
-
"text": f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"
|
225 |
-
}
|
226 |
-
]
|
227 |
-
}
|
228 |
-
],
|
229 |
-
"generationConfig": {
|
230 |
-
"temperature": temperature,
|
231 |
-
"maxOutputTokens": 2048
|
232 |
-
}
|
233 |
-
}
|
234 |
|
235 |
-
#
|
236 |
-
|
237 |
-
response = requests.post(url, headers=headers, json=payload)
|
238 |
|
239 |
-
#
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
|
|
|
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
except Exception as e:
|
256 |
-
|
257 |
-
|
|
|
|
|
258 |
|
259 |
def ask_question(self, question, context=None, temperature=0.3):
|
260 |
"""
|
|
|
3 |
import logging
|
4 |
import requests
|
5 |
from typing import Dict, List, Any, Optional
|
6 |
+
|
7 |
+
|
8 |
from datetime import datetime, timedelta
|
9 |
|
10 |
logger = logging.getLogger(__name__)
|
|
|
202 |
if not self.api_key:
|
203 |
return "Не вказано API ключ Gemini"
|
204 |
|
205 |
+
# Імпортуємо необхідні бібліотеки
|
206 |
+
try:
|
207 |
+
from google import genai
|
208 |
+
from google.genai import types
|
209 |
+
except ImportError:
|
210 |
+
logger.error("Бібліотека google-generativeai не встановлена")
|
211 |
+
return "Помилка: бібліотека google-generativeai не встановлена. Встановіть її командою: pip install google-generativeai"
|
212 |
+
|
213 |
+
# Налаштування клієнта
|
214 |
+
client = genai.Client(api_key=self.api_key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
+
# Додаємо логування для діагностики
|
217 |
+
logger.info(f"Відправляємо запит до Gemini API. Довжина запиту: {len(data_summary)} символів")
|
|
|
218 |
|
219 |
+
# Системна інструкція
|
220 |
+
system_instruction = """Ви аналітик Jira з досвідом у процесах розробки ПЗ.
|
221 |
+
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
|
222 |
+
для покращення процесу. Будьте конкретними та орієнтованими на дії.
|
223 |
+
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
|
224 |
+
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""
|
225 |
+
|
226 |
+
# Запит користувача
|
227 |
+
user_prompt = f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"
|
228 |
+
|
229 |
+
# Створення запиту
|
230 |
+
contents = [
|
231 |
+
types.Content(
|
232 |
+
role="user",
|
233 |
+
parts=[types.Part.from_text(text=user_prompt)]
|
234 |
+
)
|
235 |
+
]
|
236 |
|
237 |
+
# Налаштування генерації з системною інструкцією
|
238 |
+
generate_content_config = types.GenerateContentConfig(
|
239 |
+
temperature=temperature,
|
240 |
+
top_p=0.95,
|
241 |
+
top_k=40,
|
242 |
+
max_output_tokens=2048,
|
243 |
+
response_mime_type="text/plain",
|
244 |
+
system_instruction=[
|
245 |
+
types.Part.from_text(text=system_instruction)
|
246 |
+
],
|
247 |
+
)
|
248 |
+
|
249 |
+
# Відправка запиту
|
250 |
+
try:
|
251 |
+
response = client.models.generate_content(
|
252 |
+
model=self.model,
|
253 |
+
contents=contents,
|
254 |
+
config=generate_content_config,
|
255 |
+
)
|
256 |
+
|
257 |
+
# Перевірка відповіді
|
258 |
+
if hasattr(response, 'text') and response.text:
|
259 |
+
logger.info("Успішно отримано аналіз від Gemini")
|
260 |
+
return response.text
|
261 |
+
else:
|
262 |
+
logger.error("Порожня відповідь від Gemini API")
|
263 |
+
return "Помилка: порожня відповідь від Gemini API"
|
264 |
+
|
265 |
+
except Exception as api_error:
|
266 |
+
logger.error(f"Помилка API Gemini: {str(api_error)}")
|
267 |
+
|
268 |
+
# Спробуємо з іншою моделлю, якщо поточна не працює
|
269 |
+
try:
|
270 |
+
logger.info("Спроба з альтернативною моделлю gemini-1.5-flash")
|
271 |
+
alternative_model = "gemini-1.5-flash"
|
272 |
+
response = client.models.generate_content(
|
273 |
+
model=alternative_model,
|
274 |
+
contents=contents,
|
275 |
+
config=generate_content_config,
|
276 |
+
)
|
277 |
+
|
278 |
+
if hasattr(response, 'text') and response.text:
|
279 |
+
logger.info("Успішно отримано аналіз від альтернативної моделі Gemini")
|
280 |
+
return response.text
|
281 |
+
else:
|
282 |
+
return "Помилка: порожня відповідь від альтернативної моделі Gemini API"
|
283 |
+
except Exception as retry_error:
|
284 |
+
logger.error(f"Повторна помилка API Gemini: {str(retry_error)}")
|
285 |
+
return f"Помилка при взаємодії з Gemini: {str(api_error)}"
|
286 |
+
|
287 |
except Exception as e:
|
288 |
+
import traceback
|
289 |
+
error_msg = f"Помилка при взаємодії з Gemini: {str(e)}\n\n{traceback.format_exc()}"
|
290 |
+
logger.error(error_msg)
|
291 |
+
return error_msg
|
292 |
|
293 |
def ask_question(self, question, context=None, temperature=0.3):
|
294 |
"""
|
requirements.txt
CHANGED
@@ -7,6 +7,6 @@ seaborn>=0.12.2
|
|
7 |
python-dotenv>=1.0.0
|
8 |
markdown>=3.4.4
|
9 |
pathlib>=1.0.1
|
10 |
-
google-
|
11 |
openai>=1.12.0
|
12 |
httpx>=0.27.0
|
|
|
7 |
python-dotenv>=1.0.0
|
8 |
markdown>=3.4.4
|
9 |
pathlib>=1.0.1
|
10 |
+
google-genai>=0.3.2
|
11 |
openai>=1.12.0
|
12 |
httpx>=0.27.0
|