Add appending of external files, fiz appending of internal files - now via metadata (attachments)
This commit is contained in:
parent
b0209d6b8e
commit
899b0f3d5e
|
|
@ -197,22 +197,22 @@ def get_available_models():
|
|||
|
||||
@api.route('/api/chat/send', methods=['POST'])
|
||||
def send_user_message():
|
||||
"""API endpoint для создания узла пользователя и получения graph_id."""
|
||||
"""API endpoint для создания узла пользователя с вложениями."""
|
||||
data = request.get_json()
|
||||
message = data.get("message")
|
||||
graph_id = data.get("graph_id")
|
||||
parent_node_id = data.get("parent_node_id")
|
||||
attachments = data.get("attachments", []) # НОВОЕ
|
||||
|
||||
if not message:
|
||||
return jsonify({"error": "Сообщение не может быть пустым."}, 400)
|
||||
|
||||
try:
|
||||
result = graph_history_manager.create_user_node(
|
||||
message, graph_id, parent_node_id)
|
||||
|
||||
message, graph_id, parent_node_id, attachments # ИЗМЕНЕНО
|
||||
)
|
||||
graph_history_manager.title_generator.add_node_to_queue_direct(
|
||||
result.get("graph_id"), result.get("node_id"))
|
||||
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании узла пользователя: {e}")
|
||||
|
|
|
|||
|
|
@ -303,10 +303,10 @@ class GraphHistoryManager:
|
|||
return summaries
|
||||
|
||||
def get_messages_from_root_to_node(
|
||||
self,
|
||||
graph_id: str, # НОВЫЙ АРГУМЕНТ
|
||||
graph_data: Dict[str, Any],
|
||||
target_node_id: str) -> List[Dict[str, str]]:
|
||||
self,
|
||||
graph_id: str,
|
||||
graph_data: Dict[str, Any],
|
||||
target_node_id: str) -> List[Dict[str, Any]]: # ИЗМЕНЕНО: Dict[str, str] -> Dict[str, Any]
|
||||
"""
|
||||
Получает список сообщений от корневого узла до указанного узла.
|
||||
Сообщения извлекаются из данных узлов, а не из отдельного поля.
|
||||
|
|
@ -314,9 +314,7 @@ class GraphHistoryManager:
|
|||
all_nodes = graph_data.get("graph_nodes", [])
|
||||
edges = graph_data.get("graph_edges", [])
|
||||
|
||||
# Создаем словарь для быстрого поиска узлов по ID
|
||||
node_map = {node['id']: node for node in all_nodes}
|
||||
# Создаем словарь для быстрого поиска родительских узлов
|
||||
parent_map = {edge['target']: edge['source'] for edge in edges}
|
||||
|
||||
def get_path_to_root(node_id: str) -> List[str]:
|
||||
|
|
@ -324,33 +322,33 @@ 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)
|
||||
# Собираем сообщения, соответствующие узлам в пути.
|
||||
# Сообщения теперь хранятся в 'data' каждого узла при создании.
|
||||
|
||||
messages_for_path = []
|
||||
for node_id in path_to_target:
|
||||
node = node_map.get(node_id)
|
||||
if node and 'message' in node.get(
|
||||
'data', {}): # Проверяем наличие поля 'message'
|
||||
if node and 'message' in node.get('data', {}):
|
||||
original_message = node['data']['message']
|
||||
messages_for_path.append({
|
||||
"role":
|
||||
original_message.get("role", "unknown"),
|
||||
"type":
|
||||
node.get("type", "unknown"),
|
||||
"content":
|
||||
original_message.get("content", ""),
|
||||
"node_id":
|
||||
original_message.get("node_id", node_id),
|
||||
|
||||
message_dict = {
|
||||
"role": original_message.get("role", "unknown"),
|
||||
"type": node.get("type", "unknown"),
|
||||
"content": original_message.get("content", ""),
|
||||
"node_id": original_message.get("node_id", node_id),
|
||||
"graph_id": graph_id
|
||||
})
|
||||
}
|
||||
|
||||
# НОВОЕ: Добавляем attachments, если они есть
|
||||
if "attachments" in original_message:
|
||||
message_dict["attachments"] = original_message["attachments"]
|
||||
|
||||
messages_for_path.append(message_dict)
|
||||
|
||||
return messages_for_path
|
||||
|
||||
|
|
@ -491,13 +489,12 @@ class GraphHistoryManager:
|
|||
print(f"Обновлено строк: {updated_rows}")
|
||||
conn.commit()
|
||||
|
||||
def create_user_node(self, message: str, graph_id: Optional[str] = None, parent_node_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Создаёт узел пользователя и возвращает graph_id и node_id.
|
||||
"""
|
||||
def create_user_node(self, message: str, graph_id: Optional[str] = None,
|
||||
parent_node_id: Optional[str] = None,
|
||||
attachments: List[Dict[str, Any]] = None) -> Dict[str, Any]: # ИЗМЕНЕНО
|
||||
"""Создаёт узел пользователя с вложениями."""
|
||||
if not graph_id:
|
||||
graph_id = str(uuid.uuid4())
|
||||
|
||||
if not self._ensure_graph_exists(graph_id):
|
||||
raise Exception(f"Граф с ID {graph_id} не найден.")
|
||||
|
||||
|
|
@ -507,7 +504,12 @@ class GraphHistoryManager:
|
|||
try:
|
||||
node_data = {
|
||||
"label": message[:30] + "..." if len(message) > 30 else message,
|
||||
"message": {"role": "user", "content": message, "node_id": user_node_id}
|
||||
"message": {
|
||||
"role": "user",
|
||||
"content": message,
|
||||
"node_id": user_node_id,
|
||||
"attachments": attachments or [] # НОВОЕ
|
||||
}
|
||||
}
|
||||
|
||||
self._add_graph_node(conn, graph_id, {
|
||||
|
|
|
|||
|
|
@ -92,45 +92,61 @@ app = workflow.compile()
|
|||
|
||||
|
||||
def run_agent_streaming(graph_id: str,
|
||||
user_node_id: str,
|
||||
assistant_node_id: str,
|
||||
system_prompt: Optional[str] = None,
|
||||
model: Optional[str] = None):
|
||||
"""
|
||||
Генератор для стримингового ответа LLM.
|
||||
Возвращает чанки контента по мере их получения.
|
||||
"""
|
||||
user_node_id: str,
|
||||
assistant_node_id: str,
|
||||
system_prompt: Optional[str] = None,
|
||||
model: Optional[str] = None):
|
||||
"""Генератор для стримингового ответа LLM с поддержкой вложений."""
|
||||
try:
|
||||
# Загружаем граф и историю до user_node_id
|
||||
loaded_graph_data = graph_history_manager.get_graph(
|
||||
graph_id, target_node_id=user_node_id)
|
||||
|
||||
if not loaded_graph_data:
|
||||
yield {"type": "error", "error": "Граф не найден."}
|
||||
return
|
||||
|
||||
messages = loaded_graph_data.get("messages", [])
|
||||
|
||||
# Формируем сообщения для LLM
|
||||
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
|
||||
|
||||
messages_for_llm = []
|
||||
if system_prompt:
|
||||
messages_for_llm.append(SystemMessage(content=system_prompt))
|
||||
|
||||
for msg in messages:
|
||||
# ИЗМЕНЕНО: Обрабатываем вложения
|
||||
for i, msg in enumerate(messages):
|
||||
is_last = (i == len(messages) - 1)
|
||||
|
||||
if msg["role"] == "user":
|
||||
messages_for_llm.append(HumanMessage(content=msg["content"]))
|
||||
content = msg["content"]
|
||||
attachments = msg.get("attachments", [])
|
||||
|
||||
# Раскрываем постоянные вложения и вложения последнего сообщения
|
||||
if attachments:
|
||||
for att in attachments:
|
||||
if att.get("permanent") or is_last:
|
||||
formatted_content = format_attachment_for_llm(att)
|
||||
content += "\n" + formatted_content
|
||||
|
||||
messages_for_llm.append(HumanMessage(content=content))
|
||||
|
||||
elif msg["role"] == "assistant":
|
||||
messages_for_llm.append(AIMessage(content=msg["content"]))
|
||||
|
||||
# Получаем LLM и стримим ответ
|
||||
# Стриминг ответа
|
||||
llm = get_llm(model or "gemini-2.5-flash")
|
||||
|
||||
for chunk in llm.stream2(messages_for_llm):
|
||||
content = chunk.content if hasattr(chunk,
|
||||
'content') else str(chunk)
|
||||
content = chunk.content if hasattr(chunk, 'content') else str(chunk)
|
||||
yield {"type": "chunk", "content": content}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при стриминге: {e}")
|
||||
yield {"type": "error", "error": str(e)}
|
||||
|
||||
|
||||
def format_attachment_for_llm(attachment: Dict[str, Any]) -> str:
|
||||
"""Форматирует вложение для вставки в сообщение LLM."""
|
||||
att_type = "промпта" if attachment["type"] == "prompt" else "файла"
|
||||
header = f"\n\n--- Содержимое {att_type}: {attachment['name']} ---\n"
|
||||
footer = f"\n--- Конец {att_type}: {attachment['name']} ---\n\n"
|
||||
return header + attachment.get("content", "").strip() + footer
|
||||
8
src/types/electron.d.ts
vendored
Normal file
8
src/types/electron.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Добавьте этот файл, если его нет
|
||||
interface Window {
|
||||
electron?: {
|
||||
shell: {
|
||||
openPath: (path: string) => Promise<string>;
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user