From 56751ebc1ca3dd4ed5a0c71fc70b8d2493dc21c7 Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Sun, 5 Oct 2025 00:43:24 +0300 Subject: [PATCH] Add deletion --- app/api.py | 18 +++++++- app/graph_history_manager.py | 80 ++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/app/api.py b/app/api.py index c6dd29a..c7107e5 100644 --- a/app/api.py +++ b/app/api.py @@ -64,6 +64,22 @@ def delete_graph(graph_id): return jsonify({"error": f"Не удалось удалить граф {graph_id}."}, 500) +@api.route('/api/graphs//nodes/', methods=['DELETE']) +def delete_graph_node(graph_id, node_id): + """API endpoint для удаления узла графа.""" + if not graph_id or not node_id: + return jsonify({"error": "Не указаны ID графа или узла для удаления."}, 400) + + try: + if graph_history_manager.delete_node(graph_id, node_id): + return jsonify({"message": f"Узел {node_id} успешно удален из графа {graph_id}."}) + else: + return jsonify({"error": f"Не удалось удалить узел {node_id} из графа {graph_id}."}, 500) + except Exception as e: + print(f"Ошибка при удалении узла {node_id} из графа {graph_id}: {e}") + return jsonify({"error": f"Ошибка сервера при удалении узла: {str(e)}"}, 500) + + @api.route('/api/messages//', methods=['GET']) def get_messages_from_root_to_node(graph_id, node_id): """API endpoint для получения сообщений от корня до выбранной ноды.""" @@ -78,7 +94,7 @@ def get_messages_from_root_to_node(graph_id, node_id): return jsonify({"error": "В графе нет активного узла, до которого можно было бы получить сообщения."}, 404) messages = graph_history_manager.get_messages_from_root_to_node( - graph_data, target_node_id) + graph_id, graph_data, target_node_id) return jsonify(messages) @api.route('/api/models', methods=['GET']) diff --git a/app/graph_history_manager.py b/app/graph_history_manager.py index ef626e9..0a27156 100644 --- a/app/graph_history_manager.py +++ b/app/graph_history_manager.py @@ -232,6 +232,7 @@ class GraphHistoryManager: resolved_current_node_id = target_node_id if target_node_id else db_current_node_id messages = self.get_messages_from_root_to_node( + graph_id, { "graph_nodes": graph_nodes, "graph_edges": graph_edges @@ -269,6 +270,7 @@ class GraphHistoryManager: # Получаем все сообщения до current_node_id full_messages = self.get_messages_from_root_to_node( + gid, { "graph_nodes": graph_nodes, "graph_edges": graph_edges @@ -297,7 +299,9 @@ class GraphHistoryManager: return summaries def get_messages_from_root_to_node( - self, graph_data: Dict[str, Any], + self, + graph_id: str, # НОВЫЙ АРГУМЕНТ + graph_data: Dict[str, Any], target_node_id: str) -> List[Dict[str, str]]: """ Получает список сообщений от корневого узла до указанного узла. @@ -316,8 +320,12 @@ class GraphHistoryManager: current = node_id while current in parent_map: current = parent_map[current] + # Проверяем, чтобы не зациклиться (хотя в древовидном графе это не должно быть проблемой) + if current in path: + print(f"Обнаружен цикл при построении пути к узлу {node_id}. Путь: {path}") + break path.append(current) - return path[::-1] + return path[::-1] # Разворачиваем путь от корня до целевого узла path_to_target = get_path_to_root(target_node_id) # Собираем сообщения, соответствующие узлам в пути. @@ -332,11 +340,12 @@ class GraphHistoryManager: "role": original_message.get("role", "unknown"), "type": - node.get("type", "unknown"), # Используем 'type' узла + node.get("type", "unknown"), "content": original_message.get("content", ""), "node_id": - original_message.get("node_id", node_id) + original_message.get("node_id", node_id), + "graph_id": graph_id }) return messages_for_path @@ -357,6 +366,69 @@ class GraphHistoryManager: print(f"Ошибка при удалении графа: {e}") return False + def delete_node(self, graph_id: str, node_id: str) -> bool: + """ + Удаляет узел из графа и переподключает его родителей к его детям. + Обновляет current_node_id, если удаляемый узел был текущим. + """ + with self._get_connection() as conn: + cursor = conn.cursor() + try: + # 1. Получаем информацию о текущем узле графа + cursor.execute("SELECT current_node_id FROM graphs WHERE id = ?", (graph_id,)) + current_graph_node_id = cursor.fetchone()[0] if cursor.rowcount > 0 else None + + # 2. Находим родителей и детей удаляемого узла + # Родители + cursor.execute("SELECT source_node_id FROM graph_edges_data WHERE graph_id = ? AND target_node_id = ?", + (graph_id, node_id)) + parents = [row[0] for row in cursor.fetchall()] + + # Дети + cursor.execute("SELECT target_node_id FROM graph_edges_data WHERE graph_id = ? AND source_node_id = ?", + (graph_id, node_id)) + children = [row[0] for row in cursor.fetchall()] + + # 3. Переподключаем родителей к детям + for parent_id in parents: + for child_id in children: + # Создаем новое ребро между родителем и ребенком + new_edge_id = str(uuid.uuid4()) + cursor.execute( + "INSERT OR IGNORE INTO graph_edges_data (graph_id, edge_id, source_node_id, target_node_id) VALUES (?, ?, ?, ?)", + (graph_id, new_edge_id, parent_id, child_id)) + + # 4. Удаляем все ребра, связанные с удаляемым узлом (входящие и исходящие) + cursor.execute("DELETE FROM graph_edges_data WHERE graph_id = ? AND (source_node_id = ? OR target_node_id = ?)", + (graph_id, node_id, node_id)) + + # 5. Удаляем сам узел + cursor.execute("DELETE FROM graph_nodes_data WHERE graph_id = ? AND node_id = ?", + (graph_id, node_id)) + node_deleted = cursor.rowcount > 0 + + # TODO: Здесь не полная логика, т.к. если родителей нет, то current_node_id=None останется + # 6. Обновляем current_node_id, если удаленный узел был текущим + if node_id == current_graph_node_id: + new_current_node_id = None + if parents: + # Если есть родители, делаем текущим узлом одного из родителей + # Для простоты, берем первого родителя. Можно добавить более сложную логику, + # например, последнего добавленного родителя, если timestamp доступен. + new_current_node_id = parents[0] + # Если детей нет и родителей тоже, current_node_id останется None. + + cursor.execute("UPDATE graphs SET current_node_id = ? WHERE id = ?", + (new_current_node_id, graph_id)) + print(f"current_node_id для графа {graph_id} обновлен на {new_current_node_id}") + + conn.commit() + return node_deleted + except Exception as e: + conn.rollback() + print(f"Ошибка при удалении узла {node_id} из графа {graph_id}: {e}") + raise # Пробрасываем исключение, чтобы API мог вернуть 500 + # ----------------------------------------------- Title Update Methods ------------------------------------------------------------ def update_graph_title(self, graph_id: str, title: str):