上传文件至 /

This commit is contained in:
2025-06-06 16:41:50 +08:00
commit ffdeafa791
5 changed files with 2146 additions and 0 deletions

432
file_manager.py Normal file
View File

@ -0,0 +1,432 @@
# 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", "通用记忆集")
)