Files
gemini_boy/file_manager.py
2025-06-06 16:41:50 +08:00

433 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# file_manager.py
import json
import os
import uuid
import shutil # 用于删除目录及其内容
import logging
from config import get_config # 导入config模块的获取配置函数
logger = logging.getLogger(__name__)
# --- 辅助函数JSON 和 JSONL 文件操作 ---
def _load_json_file(file_path, default_content=None):
"""
加载JSON文件。如果文件不存在则创建并写入默认内容如果提供
:param file_path: JSON文件路径。
:param default_content: 文件不存在时要写入的默认Python对象。
:return: 文件内容Python对象或None如果加载失败
"""
if not os.path.exists(file_path):
os.makedirs(os.path.dirname(file_path), exist_ok=True) # 确保目录存在
if default_content is not None:
logger.info(f"文件不存在,创建并写入默认内容到:{file_path}")
_save_json_file(file_path, default_content)
else:
logger.warning(f"尝试加载的JSON文件不存在且未提供默认内容{file_path}")
return {} # 返回空字典,以提高健壮性
try:
with open(file_path, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError as e:
logger.error(f"JSON文件解码失败{file_path} - {e}")
return None
except IOError as e:
logger.error(f"读取JSON文件失败{file_path} - {e}")
return None
def _save_json_file(file_path, data):
"""
保存Python对象到JSON文件。
:param file_path: JSON文件路径。
:param data: 要保存的Python对象。
"""
os.makedirs(os.path.dirname(file_path), exist_ok=True) # 确保目录存在
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
logger.debug(f"JSON文件已保存{file_path}")
return True
except IOError as e:
logger.error(f"保存JSON文件失败{file_path} - {e}")
return False
def _append_jsonl_file(file_path, data):
"""
追加一条JSON记录到JSONL文件。
:param file_path: JSONL文件路径。
:param data: 要追加的Python对象。
"""
os.makedirs(os.path.dirname(file_path), exist_ok=True) # 确保目录存在
try:
with open(file_path, "a", encoding="utf-8") as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")
logger.debug(f"JSONL记录已追加到{file_path}")
return True
except IOError as e:
logger.error(f"追加JSONL文件失败{file_path} - {e}")
return False
def _read_jsonl_file(file_path, limit=None):
"""
读取JSONL文件的所有记录。
:param file_path: JSONL文件路径。
:param limit: 可选限制读取的最后N条记录。
:return: 包含所有记录的列表。
"""
if not os.path.exists(file_path):
return []
records = []
try:
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
if limit:
lines = lines[-limit:] # 只读取最后N行
for line in lines:
try:
records.append(json.loads(line.strip()))
except json.JSONDecodeError as e:
logger.warning(
f"跳过JSONL文件中的损坏行{file_path} - {line.strip()} - {e}"
)
return records
except IOError as e:
logger.error(f"读取JSONL文件失败{file_path} - {e}")
return []
# --- 默认文件内容 ---
# 默认特征文件内容
DEFAULT_FEATURE_CONTENT = {
"角色名称": "通用AI助手",
"角色描述": "你是一个有帮助的AI助手旨在提供清晰、准确、友好的回答。你没有预设的特定角色或个性能够灵活适应各种对话主题。",
"心理特征": {
"核心哲学与世界观": {"描述": "以提供信息、解决问题为核心,保持中立客观。"},
"价值观与道德观": {"描述": "遵循伦理原则,避免偏见,确保信息准确。"},
"决策风格": {"描述": "基于逻辑和可用信息进行决策。"},
"情绪反应与表达": {"描述": "保持平静、专业、不带个人情感。"},
"人际互动与关系处理": {"描述": "以帮助用户为目标,保持礼貌和尊重。"},
"动机与目标": {"描述": "提供有价值的信息和解决方案。"},
"自我认知": {"描述": "认知自己是AI不具备人类情感和意识。"},
"应对机制": {"描述": "遇到未知问题时,会请求更多信息或承认能力限制。"},
},
"语言特征": {
"词汇与措辞": {"描述": "清晰、简洁、准确,避免模糊不清的表达。"},
"句式结构与复杂度": {"描述": "常用清晰直接的句式,逻辑性强。"},
"语气与风格": {"描述": "专业、友善、乐于助人。"},
"修辞手法与模式": {"描述": "多使用直接陈述、解释说明。"},
"互动模式": {"描述": "响应式,根据用户输入提供信息或引导对话。"},
},
"重要人际关系": [],
"角色弧线总结": "作为一个持续学习和改进的AI不断提升服务质量。",
}
# 默认记忆文件内容
DEFAULT_MEMORY_CONTENT = {
"long_term_facts": ["此记忆集为默认记忆未包含特定用户或AI的长期事实。"],
"short_term_events": [],
}
# --- 路径管理函数 ---
def _get_dir_path(base_dir_config_key):
"""根据配置获取目录路径"""
config = get_config()
return config["Application"][base_dir_config_key]
def get_features_dir():
"""获取特征文件根目录"""
return _get_dir_path("FEATURES_DIR")
def get_memories_dir():
"""获取记忆文件根目录"""
return _get_dir_path("MEMORIES_DIR")
def get_chat_logs_dir():
"""获取聊天记录文件根目录"""
return _get_dir_path("CHAT_LOGS_DIR")
def get_role_features_file_path(role_id):
"""获取指定角色ID的特征文件路径"""
return os.path.join(get_features_dir(), role_id, "features.json")
def get_memory_data_file_path(memory_id):
"""获取指定记忆ID的记忆数据文件路径"""
return os.path.join(get_memories_dir(), memory_id, "memory.json")
def get_chat_log_file_path(chat_log_id):
"""获取指定聊天记录ID的聊天记录文件路径"""
# 聊天记录ID通常与角色ID和记忆ID关联或者直接就是session ID
# 这里简化为直接以chat_log_id为文件名
return os.path.join(get_chat_logs_dir(), f"{chat_log_id}.jsonl")
# --- 角色和记忆管理 ---
def list_roles():
"""
列出所有可用的角色ID和名称。
角色名称从其features.json中获取。
:return: 列表,每个元素为 {"id": "role_id", "name": "角色名称"}
"""
roles = []
features_dir = get_features_dir()
if not os.path.exists(features_dir):
os.makedirs(features_dir) # 确保目录存在
return []
for role_id in os.listdir(features_dir):
role_path = os.path.join(features_dir, role_id)
features_file = get_role_features_file_path(role_id)
if os.path.isdir(role_path) and os.path.exists(features_file):
features = _load_json_file(features_file)
if features:
roles.append({"id": role_id, "name": features.get("角色名称", role_id)})
else:
logger.warning(
f"无法加载角色 {role_id} 的 features.json可能文件损坏。"
)
else:
logger.warning(f"目录 {role_path} 或文件 {features_file} 不完整,跳过。")
return roles
def create_role(role_id, role_name=None):
"""
创建一个新的角色。
:param role_id: 新角色的唯一ID。
:param role_name: 新角色的名称如果未提供则默认为role_id。
:return: True如果成功False如果失败或ID已存在。
"""
role_path = os.path.join(get_features_dir(), role_id)
if os.path.exists(role_path):
logger.warning(f"角色ID '{role_id}' 已存在,无法创建。")
return False
os.makedirs(role_path)
features_file = get_role_features_file_path(role_id)
default_content = DEFAULT_FEATURE_CONTENT.copy()
default_content["角色名称"] = role_name if role_name else role_id
success = _save_json_file(features_file, default_content)
if success:
logger.info(f"角色 '{role_id}' 创建成功。")
return success
def delete_role(role_id):
"""
删除一个角色及其所有相关文件。
:param role_id: 要删除的角色ID。
:return: True如果成功False如果失败。
"""
role_path = os.path.join(get_features_dir(), role_id)
if not os.path.exists(role_path):
logger.warning(f"角色ID '{role_id}' 不存在,无法删除。")
return False
try:
shutil.rmtree(role_path)
logger.info(f"角色 '{role_id}' 已成功删除。")
return True
except Exception as e:
logger.error(f"删除角色 '{role_id}' 失败: {e}")
return False
def role_exists(role_id):
"""
检查指定角色ID的角色是否存在。
:param role_id: 角色ID。
:return: True如果存在False如果不存在。
"""
return os.path.exists(get_role_features_file_path(role_id))
def list_memories():
"""
列出所有可用的记忆ID和名称。
记忆名称从其memory.json中获取如果模型能生成name字段否则使用ID
:return: 列表,每个元素为 {"id": "memory_id", "name": "记忆名称"}
"""
memories = []
memories_dir = get_memories_dir()
if not os.path.exists(memories_dir):
os.makedirs(memories_dir) # 确保目录存在
return []
for memory_id in os.listdir(memories_dir):
memory_path = os.path.join(memories_dir, memory_id)
memory_file = get_memory_data_file_path(memory_id)
if os.path.isdir(memory_path) and os.path.exists(memory_file):
memory_data = _load_json_file(memory_file)
# 确保 memory_data 不是 None 且不是空字典
if memory_data is not None and memory_data != {}:
# 假设 memory.json 中可能有一个 'name' 字段来标识记忆集
memories.append(
{"id": memory_id, "name": memory_data.get("name", memory_id)}
)
else:
logger.warning(
f"无法加载记忆 {memory_id} 的 memory.json可能文件损或为空或内容为空。"
)
else:
logger.warning(f"目录 {memory_path} 或文件 {memory_file} 不完整,跳过。")
return memories
def create_memory(memory_id, memory_name=None):
"""
创建一个新的记忆集。
:param memory_id: 新记忆集的唯一ID。
:param memory_name: 新记忆集的名称如果未提供则默认为memory_id。
:return: True如果成功False如果失败或ID已存在。
"""
memory_path = os.path.join(get_memories_dir(), memory_id)
if os.path.exists(memory_path):
logger.warning(f"记忆ID '{memory_id}' 已存在,无法创建。")
return False
os.makedirs(memory_path)
memory_file = get_memory_data_file_path(memory_id)
default_content = DEFAULT_MEMORY_CONTENT.copy()
if memory_name:
default_content["name"] = memory_name # 记忆集本身可以有一个名称
# 更新 long_term_facts 中的默认描述,包含记忆名称
if default_content["long_term_facts"]:
default_content["long_term_facts"][
0
] = f"此记忆集名为 '{memory_name}'目前未包含特定用户或AI的长期事实。"
else:
default_content["long_term_facts"].append(
f"此记忆集名为 '{memory_name}'目前未包含特定用户或AI的长期事实。"
)
success = _save_json_file(memory_file, default_content)
if success:
logger.info(f"记忆集 '{memory_id}' 创建成功。")
return success
def delete_memory(memory_id):
"""
删除一个记忆集及其所有相关文件。
:param memory_id: 要删除的记忆ID。
:return: True如果成功False如果失败。
"""
memory_path = os.path.join(get_memories_dir(), memory_id)
if not os.path.exists(memory_path):
logger.warning(f"记忆ID '{memory_id}' 不存在,无法删除。")
return False
try:
shutil.rmtree(memory_path)
logger.info(f"记忆集 '{memory_id}' 已成功删除。")
return True
except Exception as e:
logger.error(f"删除记忆集 '{memory_id}' 失败: {e}")
return False
def memory_exists(memory_id):
"""
检查指定记忆ID的记忆集是否存在。
:param memory_id: 记忆ID。
:return: True如果存在False如果不存在。
"""
return os.path.exists(get_memory_data_file_path(memory_id))
# --- 加载/保存当前活动会话的文件 ---
def load_active_features(role_id):
"""加载当前激活角色的特征数据"""
file_path = get_role_features_file_path(role_id)
return _load_json_file(file_path, DEFAULT_FEATURE_CONTENT.copy())
def save_active_features(role_id, features_data):
"""保存当前激活角色的特征数据"""
file_path = get_role_features_file_path(role_id)
return _save_json_file(file_path, features_data)
def load_active_memory(memory_id):
"""加载当前激活记忆集的记忆数据"""
file_path = get_memory_data_file_path(memory_id)
return _load_json_file(file_path, DEFAULT_MEMORY_CONTENT.copy())
def save_active_memory(memory_id, memory_data):
"""保存当前激活记忆集的记忆数据"""
file_path = get_memory_data_file_path(memory_id)
return _save_json_file(file_path, memory_data)
def append_chat_log(chat_log_id, entry):
"""向当前激活聊天记录文件追加条目"""
file_path = get_chat_log_file_path(chat_log_id)
return _append_jsonl_file(file_path, entry)
def read_chat_log(chat_log_id, limit=None):
"""读取当前激活聊天记录的所有条目"""
file_path = get_chat_log_file_path(chat_log_id)
return _read_jsonl_file(file_path, limit)
def delete_chat_log(chat_log_id):
"""删除指定聊天记录文件"""
file_path = get_chat_log_file_path(chat_log_id)
if os.path.exists(file_path):
try:
os.remove(file_path)
logger.info(f"聊天记录文件 '{file_path}' 已删除。")
return True
except OSError as e:
logger.error(f"删除聊天记录文件 '{file_path}' 失败: {e}")
return False
logger.warning(f"聊天记录文件 '{file_path}' 不存在,无需删除。")
return False
# --- 确保默认角色和记忆存在 ---
def ensure_initial_data():
"""
确保 'default_role''default_memory' 存在,如果它们不存在。
这个函数会在应用启动时调用。
"""
config_data = get_config()["Session"]
default_role_id = config_data["CURRENT_ROLE_ID"]
default_memory_id = config_data["CURRENT_MEMORY_ID"]
if not os.path.exists(get_role_features_file_path(default_role_id)):
logger.info(f"默认角色 '{default_role_id}' 不存在,正在创建。")
create_role(default_role_id, config_data.get("DEFAULT_ROLE_NAME", "通用AI助手"))
if not os.path.exists(get_memory_data_file_path(default_memory_id)):
logger.info(f"默认记忆集 '{default_memory_id}' 不存在,正在创建。")
create_memory(
default_memory_id, config_data.get("DEFAULT_MEMORY_NAME", "通用记忆集")
)