refactor(core): удаление дубликата ai_engine, единый контур SQLite и фикс флага крона
This commit is contained in:
@@ -1,178 +0,0 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/ai_engine/agent.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: modules / ai_engine
|
||||
ROLE: Лаконичный нативный оркестратор Function Calling, диспетчер handlers
|
||||
и менеджер свободных диалогов (Topic Drift).
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[AGENT_PIPELINE_ENTRY]: Главная точка входа process_chat_message.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from typing import List, Dict, Any, Tuple, Optional
|
||||
|
||||
from modules.web_api.llm.schemas import TOOLS_SCHEMA
|
||||
from modules.web_api.llm.core.ollama_client import call_ollama_chat
|
||||
from modules.web_api.llm.core.fast_path import handle_fast_path_intercept
|
||||
from modules.web_api.llm.core.tool_injector import clean_raw_tool_tags, clean_output, inject_tools_if_needed
|
||||
from modules.web_api.llm.core.context_manager import mark_last_user_message_ephemeral, close_tool_session_and_cleanup
|
||||
from modules.web_api.llm.db.db_chat import db_save_chat_message, db_get_chat_history, db_purge_ephemeral_messages
|
||||
from modules.web_api.llm.db.db_prompts import (
|
||||
db_get_session_state, db_clear_session_state, db_set_session_state,
|
||||
db_get_stats, db_get_anomalies, db_get_reference
|
||||
)
|
||||
from services.knowledge.service import get_rules
|
||||
|
||||
from .context_builder import build_agent_system_context
|
||||
from .handlers.task_handler import handle_tasks_call
|
||||
from .handlers.prompt_handler import handle_prompt_call
|
||||
from .handlers.snapshot_handler import handle_snapshots_call
|
||||
|
||||
logger = logging.getLogger("AI_AGENT")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# ANCHOR[AGENT_PIPELINE_ENTRY]
|
||||
def process_chat_message(
|
||||
user_id: int,
|
||||
user_message: str,
|
||||
file_context: str = "",
|
||||
image_b64: Optional[str] = None,
|
||||
chat_history: List[Dict[str, Any]] = None,
|
||||
session_id: str = "web_session_main"
|
||||
) -> Tuple[str, List[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||||
"""Главный конвейер обработки сообщений чата."""
|
||||
logger.info(f"Сообщение от user_id={user_id}, session_id={session_id}: {user_message}")
|
||||
|
||||
session_state = db_get_session_state(session_id)
|
||||
if not session_state:
|
||||
db_purge_ephemeral_messages(session_id)
|
||||
|
||||
full_user_content = f"{user_message}\n\n[СОДЕРЖИМОЕ ПРИКРЕПЛЕННОГО ФАЙЛА]:\n{file_context}" if file_context else user_message
|
||||
|
||||
# 1. Быстрый Fast-Path перехват кнопок подтверждения
|
||||
fast_path_res = handle_fast_path_intercept(session_id, user_message, full_user_content, session_state)
|
||||
if fast_path_res:
|
||||
return fast_path_res
|
||||
|
||||
db_save_chat_message(session_id, "user", full_user_content, is_ephemeral=0)
|
||||
db_history = db_get_chat_history(session_id, limit=20)
|
||||
system_prompt = build_agent_system_context(user_id, session_state)
|
||||
|
||||
user_msg_obj = {"role": "user", "content": full_user_content}
|
||||
|
||||
try:
|
||||
if image_b64:
|
||||
user_msg_obj["images"] = [image_b64]
|
||||
messages = [{"role": "system", "content": "Строгий модуль OCR. Перепиши весь текст буква в букву."}, user_msg_obj]
|
||||
msg = call_ollama_chat(messages, is_vision=True)
|
||||
else:
|
||||
clean_history = [dict(m) for m in db_history]
|
||||
for m in clean_history: m.pop("images", None)
|
||||
messages = [{"role": "system", "content": system_prompt}] + clean_history + [user_msg_obj]
|
||||
msg = call_ollama_chat(messages, tools=TOOLS_SCHEMA, is_vision=False)
|
||||
|
||||
raw_reply = msg.get("content", "")
|
||||
tool_calls = msg.get("tool_calls", [])
|
||||
|
||||
# 2. Гибридный семантический классификатор намерений (Fallback Safety Net)
|
||||
tool_calls = inject_tools_if_needed(user_message, raw_reply, tool_calls)
|
||||
|
||||
# 3. Исполнение инструментов через изолированные handlers
|
||||
if tool_calls:
|
||||
tool = tool_calls[0]
|
||||
fn_name = tool["function"]["name"]
|
||||
fn_args = tool["function"].get("arguments", {})
|
||||
if isinstance(fn_args, str):
|
||||
try: fn_args = json.loads(fn_args)
|
||||
except Exception: fn_args = {}
|
||||
|
||||
logger.info(f"Вызов инструмента: {fn_name} с аргументами: {fn_args}")
|
||||
close_tool_session_and_cleanup(session_id, close_reason=f"ACTIVATE_{fn_name}")
|
||||
mark_last_user_message_ephemeral(session_id)
|
||||
|
||||
state_data = session_state.get("data_json") or {} if session_state else {}
|
||||
|
||||
if fn_name in ["db_get_tasks", "db_tasks_edit", "db_add_task", "db_update_task_status", "db_delete_task"]:
|
||||
return handle_tasks_call(fn_name, fn_args, user_id, session_id)
|
||||
|
||||
elif fn_name in ["db_get_system_prompt", "db_prompt_node_edit"]:
|
||||
return handle_prompt_call(fn_name, fn_args, session_id)
|
||||
|
||||
elif fn_name in ["db_get_snapshots", "db_delete_snapshots"]:
|
||||
return handle_snapshots_call(fn_name, fn_args, session_id, user_message, state_data)
|
||||
|
||||
elif fn_name == "db_get_rules":
|
||||
res_str = json.dumps(get_rules(), ensure_ascii=False)
|
||||
elif fn_name == "db_get_stats":
|
||||
res_str = json.dumps(db_get_stats(), ensure_ascii=False)
|
||||
elif fn_name == "db_get_anomalies":
|
||||
res_str = json.dumps(db_get_anomalies(limit=fn_args.get("limit", 100), date_str=fn_args.get("date_str")), ensure_ascii=False)
|
||||
elif fn_name == "db_get_reference":
|
||||
res_str = json.dumps(db_get_reference(category=fn_args.get("category")), ensure_ascii=False)
|
||||
else:
|
||||
res_str = "{}"
|
||||
|
||||
messages.append(msg)
|
||||
messages.append({"role": "tool", "content": res_str})
|
||||
sec_msg = call_ollama_chat(messages, is_vision=False)
|
||||
final_content = clean_raw_tool_tags(clean_output(sec_msg.get("content", ""))) or "Запрос выполнен."
|
||||
db_save_chat_message(session_id, "assistant", final_content, is_ephemeral=0)
|
||||
return final_content, db_get_chat_history(session_id), None
|
||||
|
||||
# 4. Обычный содержательный диалог и управление Topic Drift
|
||||
final_reply = clean_raw_tool_tags(clean_output(raw_reply)) or "Запрос обработан."
|
||||
for artifact in ["почемучка,", "почемучка!", "почемучка?", "почемучка", "почемучто,", "почемучто", "почему-то"]:
|
||||
if final_reply.lower().startswith(artifact):
|
||||
final_reply = final_reply[len(artifact):].lstrip(",.!?:; -")
|
||||
|
||||
action_payload = None
|
||||
|
||||
# Обработка ответа "нет / спасибо" в режиме открытого инструмента
|
||||
if session_state and any(kw in user_message.lower() for kw in ["нет", "спасибо", "не надо", "готово", "хватит"]):
|
||||
close_tool_session_and_cleanup(session_id, close_reason="USER_DISMISSED_TOOL")
|
||||
db_save_chat_message(session_id, "assistant", final_reply, is_ephemeral=0)
|
||||
return final_reply, db_get_chat_history(session_id), None
|
||||
|
||||
# Инкремент счётчика шагов в сторону от инструмента (Topic Drift)
|
||||
if session_state and session_state.get("state_type") in ["PROMPT_FOLLOWUP", "PROMPT_PREVIEW", "SNAPSHOTS_VIEW"]:
|
||||
state_type = session_state.get("state_type")
|
||||
state_data = session_state.get("data_json") or {}
|
||||
if not isinstance(state_data, dict):
|
||||
state_data = {}
|
||||
|
||||
idle_turns = state_data.get("idle_turns", 0) + 1
|
||||
state_data["idle_turns"] = idle_turns
|
||||
|
||||
if idle_turns >= 4:
|
||||
# 4-й шаг не по теме: бесшумно закрываем сессию и вычищаем эфемерные карточки
|
||||
close_tool_session_and_cleanup(session_id, close_reason="TOPIC_DRIFT_TIMEOUT")
|
||||
elif idle_turns == 3:
|
||||
# 3-й шаг: выводим вежливое напоминание с кнопками
|
||||
tool_label = "системным промптом" if "PROMPT" in state_type else "снапшотами СКУД"
|
||||
guard_question = f"Желаете продолжить работу с {tool_label}?"
|
||||
final_reply += f"\n\n💡 *Напоминание:* {guard_question}"
|
||||
action_payload = {
|
||||
"type": "FOLLOW_UP_ACTION",
|
||||
"buttons": [
|
||||
{"label": "Показать снова", "value": "покажи системный промпт" if "PROMPT" in state_type else "покажи снапшоты", "style": "primary"},
|
||||
{"label": "Завершить", "value": "нет, спасибо", "style": "secondary"}
|
||||
]
|
||||
}
|
||||
db_set_session_state(session_id, state_type, state_data)
|
||||
else:
|
||||
# 1-й и 2-й шаг: фиксируем обновленный счётчик
|
||||
db_set_session_state(session_id, state_type, state_data)
|
||||
|
||||
is_ephem_reply = 1 if "актуальный системный промпт:" in final_reply.lower() else 0
|
||||
db_save_chat_message(session_id, "assistant", final_reply, is_ephemeral=is_ephem_reply)
|
||||
return final_reply, db_get_chat_history(session_id), action_payload
|
||||
|
||||
except Exception as ex:
|
||||
logger.exception(f"Ошибка в агенте: {ex}")
|
||||
return f"Внутренняя ошибка сервера: {ex}", db_get_chat_history(session_id), None
|
||||
@@ -1,84 +0,0 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/ai_engine/context_builder.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: modules / ai_engine
|
||||
ROLE: Динамическая сборка системного промпта из БД, календаря и контекста сессий.
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[CONTEXT_BUILDER_MAIN]: Формирование полного системного контекста для LLM.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from services.prompts.service import get_active_system_prompt
|
||||
from modules.web_api.llm.core.calendar_utils import get_dynamic_calendar_context
|
||||
|
||||
|
||||
# ANCHOR[CONTEXT_BUILDER_MAIN]
|
||||
def build_agent_system_context(
|
||||
user_id: int,
|
||||
session_state: Optional[Dict[str, Any]]
|
||||
) -> str:
|
||||
"""Формирует полный динамический системный контекст агента с правилами из БД."""
|
||||
|
||||
# 1. Загружаем актуальный базовый системный промпт из SQLite БД
|
||||
base_system_prompt = get_active_system_prompt()
|
||||
|
||||
# 2. Серверный календарь и контекст дат
|
||||
calendar_context = get_dynamic_calendar_context()
|
||||
user_info = f"Пользователь ID={user_id}" if user_id != 0 else "Гость"
|
||||
|
||||
# 3. Контекст текущего активного стейта сессии
|
||||
current_state_type = session_state.get("state_type") if session_state else None
|
||||
state_data = session_state.get("data_json") or {} if session_state else {}
|
||||
if not isinstance(state_data, dict):
|
||||
state_data = {}
|
||||
|
||||
active_state_context = ""
|
||||
if current_state_type == "PROMPT_PREVIEW":
|
||||
active_state_context = (
|
||||
"\n[ТЕКУЩИЙ РЕЖИМ: ПРЕДПРОСМОТР СИСТЕМНОГО ПРОМПТА]\n"
|
||||
"- Открыт предпросмотр изменений промпта. Для любых правок вызывай db_prompt_node_edit.\n"
|
||||
)
|
||||
elif current_state_type == "PROMPT_FOLLOWUP":
|
||||
active_state_context = (
|
||||
"\n[ТЕКУЩИЙ РЕЖИМ: СЕССИЯ РЕДАКТИРОВАНИЯ ПРОМПТА]\n"
|
||||
"- Оператор просматривает или редактирует системный промпт.\n"
|
||||
"- На любые команды вида 'удали пункт X.Y' или 'удали X.Y' ТЫ ОБЯЗАН ВЫЗВАТЬ db_prompt_node_edit с action='DELETE', section_id=X, item_id=Y.\n"
|
||||
"- На любые команды 'добавь пункт X.Y ...' вызывай action='ADD'.\n"
|
||||
"- Запрещено путать ADD и DELETE.\n"
|
||||
)
|
||||
elif current_state_type == "SNAPSHOTS_VIEW":
|
||||
active_date = state_data.get("query_date", "выбранную дату")
|
||||
active_state_context = (
|
||||
f"\n[ТЕКУЩИЙ РЕЖИМ: ПРОСМОТР СНАПШОТОВ СКУД]\n"
|
||||
f"- Сейчас на экране отображаются снапшоты за {active_date}.\n"
|
||||
f"- Ты можешь форматировать, фильтровать или анализировать этот текущий срез.\n"
|
||||
f"- Если оператор запрашивает ДРУГУЮ дату или день недели (например: 'за вчера', 'а за 13.08') — "
|
||||
f"ТЫ ОБЯЗАН СРАЗУ ВЫЗВАТЬ db_get_snapshots(date_str=...).\n"
|
||||
f"- Запрещено генерировать текст за другую дату по памяти.\n"
|
||||
)
|
||||
elif current_state_type == "SNAPSHOT_DELETE_CONFIRM":
|
||||
active_state_context = "\n[ВНИМАНИЕ: ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ УДАЛЕНИЯ СНАПШОТОВ]\n"
|
||||
elif current_state_type == "TASK_DELETE_CONFIRM":
|
||||
active_state_context = "\n[ВНИМАНИЕ: ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ УДАЛЕНИЯ ЗАДАЧИ]\n"
|
||||
|
||||
# 4. Сборка полного системного сообщения
|
||||
return (
|
||||
f"{base_system_prompt}\n\n"
|
||||
f"СТРОГИЕ ПРАВИЛА ВЫЗОВА ИНСТРУМЕНТОВ:\n"
|
||||
f"1. Для системного промпта:\n"
|
||||
f" - 'добавь X.Y Текст' -> СРАЗУ вызывай db_prompt_node_edit(action='ADD', section_id=X, item_id=Y, content='Текст').\n"
|
||||
f" - 'удали X.Y' -> СРАЗУ вызывай db_prompt_node_edit(action='DELETE', section_id=X, item_id=Y, content='').\n"
|
||||
f" - 'покажи системный промпт' -> СРАЗУ вызывай db_get_system_prompt().\n"
|
||||
f"2. Для задач:\n"
|
||||
f" - 'удали задачу N' -> СРАЗУ вызывай db_tasks_edit(action='DELETE', task_id='N').\n"
|
||||
f" - 'возьми в работу N' -> СРАЗУ вызывай db_tasks_edit(action='UPDATE', task_id='N', status='IN_PROGRESS').\n"
|
||||
f" - 'заверши N' / 'готово N' -> СРАЗУ вызывай db_tasks_edit(action='UPDATE', task_id='N', status='COMPLETED').\n"
|
||||
f"3. Запрещено задавать вопросы и писать подтверждения текстом — СРАЗУ вызывай соответствующий инструмент!\n\n"
|
||||
f"[СИСТЕМНЫЙ КАЛЕНДАРЬ СЕРВЕРА]\n"
|
||||
f"- Пользователь: {user_info}\n"
|
||||
f"- {calendar_context}\n"
|
||||
f"{active_state_context}"
|
||||
)
|
||||
@@ -1,98 +0,0 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/ai_engine/handlers/prompt_handler.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: modules / ai_engine / handlers
|
||||
ROLE: Изолированная обработка команд управления системным промптом.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
from services.prompts.service import get_active_system_prompt, create_prompt_preview
|
||||
from modules.web_api.llm.db.db_chat import db_save_chat_message, db_get_chat_history
|
||||
from modules.web_api.llm.db.db_prompts import db_set_session_state
|
||||
|
||||
|
||||
# ANCHOR[PROMPT_HANDLER_DISPATCH]
|
||||
def handle_prompt_call(
|
||||
fn_name: str,
|
||||
fn_args: Dict[str, Any],
|
||||
session_id: str
|
||||
) -> Tuple[str, list, Optional[Dict[str, Any]]]:
|
||||
"""Обрабатывает вызовы просмотра и изменения системного промпта."""
|
||||
|
||||
# 1. Просмотр промпта с кнопкой быстрого перехода в редактор
|
||||
if fn_name == "db_get_system_prompt":
|
||||
active_prompt = get_active_system_prompt()
|
||||
reply_text = f"Актуальный системный промпт:\n\n{active_prompt}"
|
||||
|
||||
# Сохраняем состояние сессии для возможности мгновенного редактирования и подтверждения
|
||||
db_set_session_state(session_id, "PROMPT_PREVIEW", {
|
||||
"draft_text": active_prompt,
|
||||
"action": "MANUAL_EDIT",
|
||||
"idle_turns": 0
|
||||
})
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "PROMPT_PREVIEW",
|
||||
"raw_draft": active_prompt,
|
||||
"baseline_prompt": active_prompt,
|
||||
"buttons": [
|
||||
{"label": "✏️ Редактировать", "value": "action:open_editor", "style": "secondary"},
|
||||
{"label": "Готово", "value": "нет, спасибо", "style": "secondary"}
|
||||
]
|
||||
}
|
||||
|
||||
# 2. Предпросмотр точечных или пакетных изменений (ADD / EDIT / DELETE / BATCH_DELETE)
|
||||
action = str(fn_args.get("action", "ADD")).upper()
|
||||
nodes_list = fn_args.get("nodes_list", [])
|
||||
delete_nodes_tuples = []
|
||||
|
||||
if nodes_list:
|
||||
for n_str in nodes_list:
|
||||
parts = str(n_str).strip().split(".")
|
||||
if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
||||
delete_nodes_tuples.append((int(parts[0]), int(parts[1])))
|
||||
|
||||
sec_id = None
|
||||
itm_id = None
|
||||
try:
|
||||
if fn_args.get("section_id") is not None:
|
||||
sec_id = int(str(fn_args.get("section_id")).strip())
|
||||
if fn_args.get("item_id") is not None:
|
||||
itm_id = int(str(fn_args.get("item_id")).strip())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
content = str(fn_args.get("content", "")).strip()
|
||||
|
||||
merged_prompt, diff_html, baseline_prompt = create_prompt_preview(
|
||||
action=action,
|
||||
section_id=sec_id,
|
||||
item_id=itm_id,
|
||||
content=content,
|
||||
delete_nodes=delete_nodes_tuples if delete_nodes_tuples else None
|
||||
)
|
||||
|
||||
db_set_session_state(session_id, "PROMPT_PREVIEW", {
|
||||
"draft_text": merged_prompt,
|
||||
"action": "MANUAL_EDIT",
|
||||
"section_id": sec_id,
|
||||
"item_id": itm_id,
|
||||
"content": content,
|
||||
"idle_turns": 0
|
||||
})
|
||||
|
||||
preview_reply = f"Предпросмотр изменений системного промпта:\n\n{diff_html}\n\nДля применения подтвердите действие, отредактируйте или отмените."
|
||||
db_save_chat_message(session_id, "assistant", preview_reply, is_ephemeral=1)
|
||||
return preview_reply, db_get_chat_history(session_id), {
|
||||
"type": "PROMPT_PREVIEW",
|
||||
"raw_draft": merged_prompt,
|
||||
"baseline_prompt": baseline_prompt,
|
||||
"buttons": [
|
||||
{"label": "Подтвердить", "value": "подтверждаю", "style": "primary"},
|
||||
{"label": "Отменить", "value": "отмена", "style": "danger"},
|
||||
{"label": "✏️ Редактировать", "value": "action:open_editor", "style": "secondary"}
|
||||
]
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/ai_engine/handlers/snapshot_handler.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: modules / ai_engine / handlers
|
||||
ROLE: Изолированная обработка запросов просмотра и удаления срезов СКУД.
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[SNAPSHOT_HANDLER_DISPATCH]: Обработка db_get_snapshots и db_delete_snapshots.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Tuple, Optional, List
|
||||
from services.snapshots.service import get_snapshots_registry, delete_snapshots_safely
|
||||
from modules.web_api.llm.core.calendar_utils import parse_relative_date_ru
|
||||
from modules.web_api.llm.db.db_chat import db_save_chat_message, db_get_chat_history
|
||||
from modules.web_api.llm.db.db_prompts import db_set_session_state
|
||||
|
||||
|
||||
# ANCHOR[SNAPSHOT_HANDLER_DISPATCH]
|
||||
def handle_snapshots_call(
|
||||
fn_name: str,
|
||||
fn_args: Dict[str, Any],
|
||||
session_id: str,
|
||||
user_message: str,
|
||||
state_data: Dict[str, Any]
|
||||
) -> Tuple[str, list, Optional[Dict[str, Any]]]:
|
||||
"""Обрабатывает запросы реестра и безопасного удаления срезов."""
|
||||
|
||||
# 1. Получение срезов
|
||||
if fn_name == "db_get_snapshots":
|
||||
date_param = fn_args.get("date_str")
|
||||
if not date_param and user_message:
|
||||
date_param = parse_relative_date_ru(user_message)
|
||||
|
||||
snapshots_res = get_snapshots_registry(date_str=date_param)
|
||||
query_date = snapshots_res.get("query_date", "выбранную дату")
|
||||
|
||||
db_set_session_state(session_id, "SNAPSHOTS_VIEW", snapshots_res)
|
||||
reply_text = f"Реестр срезов СКУД за {query_date}:"
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "SNAPSHOTS_CARD",
|
||||
"data": snapshots_res
|
||||
}
|
||||
|
||||
# 2. Удаление срезов (двухфазное подтверждение)
|
||||
raw_id = fn_args.get("snapshot_id") or fn_args.get("day_str")
|
||||
raw_ids = fn_args.get("snapshot_ids") or []
|
||||
is_confirmed = fn_args.get("confirmed", False)
|
||||
|
||||
if raw_id and not raw_ids:
|
||||
if isinstance(raw_id, str) and "," in raw_id:
|
||||
raw_ids = [s.strip() for s in raw_id.split(",")]
|
||||
else:
|
||||
raw_ids = [raw_id]
|
||||
|
||||
safe_ids = [s.strip() for s in raw_ids if s and not str(s).strip().startswith("Y")]
|
||||
if not safe_ids:
|
||||
reply_text = "⚠️ Итоговый срез Y защищен от удаления. Выберите дневные снапшоты."
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), None
|
||||
|
||||
if not is_confirmed:
|
||||
query_date = state_data.get("query_date", "")
|
||||
db_set_session_state(session_id, "SNAPSHOT_DELETE_CONFIRM", {
|
||||
"snapshot_ids": safe_ids,
|
||||
"query_date": query_date,
|
||||
"idle_turns": 0
|
||||
})
|
||||
ids_str = ", ".join(safe_ids)
|
||||
reply_text = f"Вы действительно хотите удалить дневные снапшоты: {ids_str}?"
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "SNAPSHOT_DELETE_CONFIRM",
|
||||
"buttons": [
|
||||
{"label": f"Удалить ({len(safe_ids)} шт.)", "value": f"подтверждаю удаление снапшотов {ids_str}", "style": "danger"},
|
||||
{"label": "Отмена", "value": "отмена", "style": "secondary"}
|
||||
]
|
||||
}
|
||||
else:
|
||||
delete_snapshots_safely(snapshot_ids=safe_ids)
|
||||
query_date = state_data.get("query_date", "")
|
||||
updated_data = get_snapshots_registry(date_str=query_date)
|
||||
|
||||
reply_text = f"✅ Успешно удалено снапшотов: {len(safe_ids)} шт."
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "SNAPSHOTS_CARD",
|
||||
"data": updated_data
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/ai_engine/handlers/task_handler.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: modules / ai_engine / handlers
|
||||
ROLE: Изолированная обработка вызовов инструментов задач (get, edit, delete, export).
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[TASK_HANDLER_DISPATCH]: Обработка вызовов db_get_tasks и db_tasks_edit.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
from services.tasks.service import get_tasks, execute_task_action
|
||||
from modules.web_api.llm.db.db_chat import db_save_chat_message, db_get_chat_history
|
||||
from modules.web_api.llm.db.db_prompts import db_set_session_state
|
||||
|
||||
|
||||
# ANCHOR[TASK_HANDLER_DISPATCH]
|
||||
def handle_tasks_call(
|
||||
fn_name: str,
|
||||
fn_args: Dict[str, Any],
|
||||
user_id: int,
|
||||
session_id: str
|
||||
) -> Tuple[str, list, Optional[Dict[str, Any]]]:
|
||||
"""Обрабатывает нативные tool-вызовы по задачам."""
|
||||
|
||||
# 1. Просмотр задач
|
||||
if fn_name == "db_get_tasks":
|
||||
raw_tasks = get_tasks(user_id, status=fn_args.get("status"))
|
||||
reply_text = "Вот интерактивный список ваших текущих задач:"
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "TASK_INTERACTIVE_CARD",
|
||||
"tasks": raw_tasks
|
||||
}
|
||||
|
||||
# 2. Модификация / Удаление / Экспорт
|
||||
action = (fn_args.get("action") or "UPDATE").upper()
|
||||
if fn_name == "db_add_task": action = "ADD"
|
||||
elif fn_name == "db_delete_task": action = "DELETE"
|
||||
elif fn_name == "db_update_task_status": action = "UPDATE"
|
||||
|
||||
# Двухфазное подтверждение удаления
|
||||
if action == "DELETE":
|
||||
task_id_raw = str(fn_args.get("task_id", "")).replace("#", "").replace("TASK-", "").strip()
|
||||
db_set_session_state(session_id, "TASK_DELETE_CONFIRM", {"task_id": task_id_raw, "idle_turns": 0})
|
||||
reply_text = f"Вы действительно хотите удалить задачу #{task_id_raw}?"
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "TASK_DELETE_CONFIRM",
|
||||
"buttons": [
|
||||
{"label": f"Удалить #{task_id_raw}", "value": f"подтверждаю удаление задачи {task_id_raw}", "style": "danger"},
|
||||
{"label": "Отмена", "value": "отмена", "style": "secondary"}
|
||||
]
|
||||
}
|
||||
|
||||
# Экспорт в Markdown
|
||||
elif action == "EXPORT":
|
||||
export_res = execute_task_action(
|
||||
user_id=user_id,
|
||||
action="EXPORT",
|
||||
filename=fn_args.get("filename"),
|
||||
status=fn_args.get("status")
|
||||
)
|
||||
reply_text = export_res.get("message", "Отчет по задачам успешно экспортирован.")
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=0)
|
||||
|
||||
action_payload = None
|
||||
if export_res.get("status") == "success":
|
||||
action_payload = {
|
||||
"type": "FILE_DOWNLOAD_CARD",
|
||||
"filename": export_res.get("filename"),
|
||||
"download_url": export_res.get("download_url"),
|
||||
"tasks_count": export_res.get("tasks_count")
|
||||
}
|
||||
return reply_text, db_get_chat_history(session_id), action_payload
|
||||
|
||||
# Создание и обновление
|
||||
else:
|
||||
res = execute_task_action(
|
||||
user_id=user_id,
|
||||
action=action,
|
||||
task_id=fn_args.get("task_id"),
|
||||
title=fn_args.get("title"),
|
||||
priority=fn_args.get("priority", "MEDIUM"),
|
||||
status=fn_args.get("status"),
|
||||
module=fn_args.get("module", "general"),
|
||||
due_date=fn_args.get("due_date")
|
||||
)
|
||||
raw_tasks = get_tasks(user_id)
|
||||
reply_text = res.get("message", "Операция над задачами выполнена.")
|
||||
db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1)
|
||||
return reply_text, db_get_chat_history(session_id), {
|
||||
"type": "TASK_INTERACTIVE_CARD",
|
||||
"tasks": raw_tasks
|
||||
}
|
||||
@@ -1,17 +1,8 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/web_api/llm/db/connection.py
|
||||
ROLE: Реэкспорт единого подключения к БД из core.connection.
|
||||
===============================================================================
|
||||
"""
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
# Динамический путь к общей БД в корне проекта
|
||||
BASE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../"))
|
||||
DB_PATH = os.path.join(BASE_ROOT, "data", "scud_orion_ai.db")
|
||||
|
||||
def get_db_connection() -> sqlite3.Connection:
|
||||
conn = sqlite3.connect(DB_PATH, timeout=30.0)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA foreign_keys = ON;")
|
||||
conn.execute("PRAGMA journal_mode = WAL;")
|
||||
conn.execute("PRAGMA synchronous = NORMAL;")
|
||||
return conn
|
||||
from core.connection import get_connection as get_db_connection, DB_PATH
|
||||
@@ -5,5 +5,5 @@ cd /home/puh/projects/scud_ai
|
||||
mkdir -p /home/puh/projects/scud_ai/logs
|
||||
|
||||
echo "[CRON REPORTS START] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_reports.log
|
||||
/home/puh/scud_orion_ai_v2/venv/bin/python /home/puh/projects/scud_ai/main_etl.py --use-existing-snapshot >> /home/puh/projects/scud_ai/logs/cron_reports.log 2>&1
|
||||
/home/puh/scud_orion_ai_v2/venv/bin/python /home/puh/projects/scud_ai/main_etl.py --skip-export >> /home/puh/projects/scud_ai/logs/cron_reports.log 2>&1
|
||||
echo "[CRON REPORTS FINISH] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_reports.log
|
||||
Reference in New Issue
Block a user