初始化仅前端版本
This commit is contained in:
46
backend/app/core/Start.py
Normal file
46
backend/app/core/Start.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import base64
|
||||||
|
from typing import Any, Dict
|
||||||
|
from IPython.core.magic_arguments import defaults
|
||||||
|
from .. import nodes
|
||||||
|
|
||||||
|
class StartNode():
|
||||||
|
name = "开始节点"
|
||||||
|
inputs = {
|
||||||
|
"user_input": "string", # 用户输入文本
|
||||||
|
"stream": "boolean", # 是否流式输出
|
||||||
|
"img_switch": "boolean", # 是否处理图片
|
||||||
|
"table_switch": "boolean", # 是否处理表格
|
||||||
|
"role_name": "string", # 角色名称
|
||||||
|
"chat_name": "string" # 会话名称
|
||||||
|
}
|
||||||
|
|
||||||
|
async def run(self, text: str = None, image: bytes = None, **kwargs) -> Dict[str, Any]:
|
||||||
|
# 查空:文本不能为空字符串
|
||||||
|
if not text or text.strip() == "":
|
||||||
|
raise ValueError("文本输入不能为空")
|
||||||
|
|
||||||
|
# 查空:图片数据不能为空
|
||||||
|
if image is None or len(image) == 0:
|
||||||
|
raise ValueError("图片输入不能为空")
|
||||||
|
|
||||||
|
# 将图片字节转换为 Base64 字符串,便于在节点间传递
|
||||||
|
image_base64 = base64.b64encode(image).decode('utf-8')
|
||||||
|
|
||||||
|
return {
|
||||||
|
"text": text,
|
||||||
|
"image": image_base64
|
||||||
|
}
|
||||||
|
|
||||||
|
async def run(is_user,floor_number,mes: str = None, stream: bool = False, img_switch: bool = False,name = "default",
|
||||||
|
table_switch: bool = False, role_name: str = None, chat_name: str = None,preset: str = None):
|
||||||
|
# 将输入内容持久化存储到本地json方便前端读
|
||||||
|
nodes.save_input_to_json(mes=mes, role_name=role_name, chat_name=chat_name, name=name, is_user=is_user, floor_number=floor_number)
|
||||||
|
# 对上一条输入内容(已确定不变的内容)调用向量化,根据role和chat嵌入到对应本地数据库
|
||||||
|
embed_input(user_input, role_name, chat_name)
|
||||||
|
# 根据role和chat去读取绑定的世界书
|
||||||
|
# 读取预设,进行拼接
|
||||||
|
# 调用模型,返回结果
|
||||||
|
# 将结果持久化存储到本地json方便前端读(用JSONL)
|
||||||
|
# 如果img_switch是开的,那么异步调用生图,并存储到目标文件夹里
|
||||||
|
# 如果table_switch是开的,那么异步调用表格生成,并存储到目标文件夹里
|
||||||
|
# 将结果返回给前端
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import base64
|
|
||||||
from core.node_base import BaseNode
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
|
|
||||||
class StartNode(BaseNode):
|
|
||||||
name = "开始节点"
|
|
||||||
inputs = {} # 没有输入参数
|
|
||||||
outputs = {
|
|
||||||
"text": "string", # 文本内容
|
|
||||||
"image": "string" # 图片以 Base64 编码的字符串传递
|
|
||||||
}
|
|
||||||
|
|
||||||
async def run(self, text: str = None, image: bytes = None, **kwargs) -> Dict[str, Any]:
|
|
||||||
# 查空:文本不能为空字符串
|
|
||||||
if not text or text.strip() == "":
|
|
||||||
raise ValueError("文本输入不能为空")
|
|
||||||
|
|
||||||
# 查空:图片数据不能为空
|
|
||||||
if image is None or len(image) == 0:
|
|
||||||
raise ValueError("图片输入不能为空")
|
|
||||||
|
|
||||||
# 将图片字节转换为 Base64 字符串,便于在节点间传递
|
|
||||||
image_base64 = base64.b64encode(image).decode('utf-8')
|
|
||||||
|
|
||||||
return {
|
|
||||||
"text": text,
|
|
||||||
"image": image_base64
|
|
||||||
}
|
|
||||||
145
backend/app/nodes/save_input_to_json.py
Normal file
145
backend/app/nodes/save_input_to_json.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import json
|
||||||
|
from typing import Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
import config as cfg
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def save_input_to_json(
|
||||||
|
mes: str,
|
||||||
|
role_name: str,
|
||||||
|
chat_name: str,
|
||||||
|
name: str,
|
||||||
|
is_user: bool,
|
||||||
|
floor_number: int = 0
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
保存消息到JSONL文件或处理重roll请求
|
||||||
|
|
||||||
|
参数:
|
||||||
|
mes: 消息内容
|
||||||
|
role_name: 角色名称
|
||||||
|
chat_name: 对话名称
|
||||||
|
name: 发送者名称
|
||||||
|
is_user: 是否为用户消息
|
||||||
|
floor_number: 楼层号(对话中的第几次回复),用于判断是否为重roll请求
|
||||||
|
|
||||||
|
返回:
|
||||||
|
更新后的消息对象
|
||||||
|
"""
|
||||||
|
config = cfg.settings
|
||||||
|
file_path = config.BASE_PATH / "data" / "chat" / role_name / f"{chat_name}.jsonl"
|
||||||
|
|
||||||
|
# 确保目录存在
|
||||||
|
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 读取文件内容
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# 判断是否为重roll请求
|
||||||
|
is_regenerate = False
|
||||||
|
target_index = -1
|
||||||
|
|
||||||
|
if lines and floor_number > 0:
|
||||||
|
# 计算当前楼层号
|
||||||
|
current_floor = len(lines)
|
||||||
|
|
||||||
|
# 如果floor_number与当前楼层号相同,则为重roll请求
|
||||||
|
if floor_number == current_floor:
|
||||||
|
# 找到最后一条非用户消息
|
||||||
|
for i in range(len(lines) - 1, -1, -1):
|
||||||
|
try:
|
||||||
|
line_data = json.loads(lines[i])
|
||||||
|
if not line_data.get('is_user', False):
|
||||||
|
is_regenerate = True
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 处理重roll逻辑
|
||||||
|
if is_regenerate:
|
||||||
|
# 解析目标消息
|
||||||
|
try:
|
||||||
|
target_message = json.loads(lines[target_index])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise ValueError(f"无法解析楼层 {floor_number} 的JSON数据")
|
||||||
|
|
||||||
|
# 初始化swipes数组
|
||||||
|
if target_message.get('swipes') is None:
|
||||||
|
target_message['swipes'] = []
|
||||||
|
|
||||||
|
# 将新回复添加到swipes数组
|
||||||
|
target_message['swipes'].append(mes)
|
||||||
|
|
||||||
|
# 更新swipe_id和content
|
||||||
|
target_message['swipes_id'] = len(target_message['swipes']) - 1
|
||||||
|
target_message['content'] = mes
|
||||||
|
|
||||||
|
# 更新文件内容
|
||||||
|
lines[target_index] = json.dumps(target_message, ensure_ascii=False) + '\n'
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
return target_message
|
||||||
|
|
||||||
|
# 处理普通消息保存逻辑
|
||||||
|
else:
|
||||||
|
# 获取当前时间
|
||||||
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
# 构建消息对象
|
||||||
|
message = {
|
||||||
|
"role": role_name,
|
||||||
|
"chat": chat_name,
|
||||||
|
"content": mes,
|
||||||
|
"name": name,
|
||||||
|
"is_user": is_user,
|
||||||
|
"send_date": current_time,
|
||||||
|
"floor_number": len(lines) + 1, # 记录楼层号
|
||||||
|
"swipes": [],
|
||||||
|
"swipes_id": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 追加到文件
|
||||||
|
with open(file_path, 'a', encoding='utf-8') as f:
|
||||||
|
f.write(json.dumps(message, ensure_ascii=False) + '\n')
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 测试普通消息保存
|
||||||
|
# save_input_to_json(
|
||||||
|
# mes="你好",
|
||||||
|
# role_name="test",
|
||||||
|
# chat_name="111",
|
||||||
|
# name="用户",
|
||||||
|
# is_user=True,
|
||||||
|
# floor_number=0
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# save_input_to_json(
|
||||||
|
# mes="你好,我是AI助手",
|
||||||
|
# role_name="test",
|
||||||
|
# chat_name="111",
|
||||||
|
# name="AI",
|
||||||
|
# is_user=False,
|
||||||
|
# floor_number=1
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 测试重roll最后一条AI消息
|
||||||
|
save_input_to_json(
|
||||||
|
mes="这是重roll后的新回复2",
|
||||||
|
role_name="test",
|
||||||
|
chat_name="111",
|
||||||
|
name="AI",
|
||||||
|
is_user=False,
|
||||||
|
floor_number=2 # 与当前楼层号相同,表示重roll
|
||||||
|
)
|
||||||
0
backend/app/tool/load_chat_history.py
Normal file
0
backend/app/tool/load_chat_history.py
Normal file
45
config.py
Normal file
45
config.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 1. 动态计算项目根目录
|
||||||
|
# 假设 config.py 位于 backend/ 目录下
|
||||||
|
# __file__ 指向本文件的绝对路径
|
||||||
|
# .parent 指向 backend/ 目录
|
||||||
|
# .parent.parent 指向项目根目录 (即包含 backend/ 和 frontend/ 的目录)
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
# 2. 加载 .env 文件
|
||||||
|
# 假设 .env 文件位于项目根目录下
|
||||||
|
load_dotenv(PROJECT_ROOT / ".env")
|
||||||
|
|
||||||
|
|
||||||
|
class Settings:
|
||||||
|
# --- 主模型配置 ---
|
||||||
|
MAIN_LLM_API_KEY = os.getenv("MAIN_LLM_API_KEY")
|
||||||
|
MAIN_LLM_MODEL = os.getenv("MAIN_LLM_MODEL", "gpt-3.5-turbo")
|
||||||
|
MAIN_LLM_BASE_URL = os.getenv("MAIN_LLM_BASE_URL", "https://api.openai.com/v1")
|
||||||
|
MAIN_LLM_MAX_TOKENS = int(os.getenv("MAIN_LLM_MAX_TOKENS", "4096"))
|
||||||
|
MAIN_LLM_STREAM = os.getenv("MAIN_LLM_STREAM", "true").lower() == "true"
|
||||||
|
|
||||||
|
# --- 路径配置 (核心修改) ---
|
||||||
|
|
||||||
|
# 强制使用计算出的项目根目录,不再依赖 .env 中的 BASE_PATH
|
||||||
|
BASE_PATH = PROJECT_ROOT
|
||||||
|
|
||||||
|
# 数据目录:固定为根目录下的 data 文件夹
|
||||||
|
# 即使 .env 里写了 DATA_PATH=/data,这里也会强制指向项目根目录下的 data
|
||||||
|
DATA_PATH = BASE_PATH / "data"
|
||||||
|
|
||||||
|
# 其他文件路径:基于 DATA_PATH 拼接
|
||||||
|
STATE_FILE = DATA_PATH / "state.json"
|
||||||
|
SCHEMA_FILE = DATA_PATH / "schema.json"
|
||||||
|
PRESETS_FILE = DATA_PATH / "presets.json"
|
||||||
|
REGEX_FILE = DATA_PATH / "regex_rules.json"
|
||||||
|
VECTORSTORE_PATH = DATA_PATH / "vectorstore"
|
||||||
|
|
||||||
|
# ... 其他配置 ...
|
||||||
|
|
||||||
|
|
||||||
|
# 实例化配置对象
|
||||||
|
settings = Settings()
|
||||||
2
data/chat/test/111.jsonl
Normal file
2
data/chat/test/111.jsonl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{"role": "test", "chat": "111", "content": "你好", "name": "用户", "is_user": true, "send_date": "2026-03-12 18:26:50", "floor_number": 1, "swipes": [], "swipes_id": 0}
|
||||||
|
{"role": "test", "chat": "111", "content": "这是重roll后的新回复2", "name": "AI", "is_user": false, "send_date": "2026-03-12 18:26:50", "floor_number": 2, "swipes": ["这是重roll后的新回复", "这是重roll后的新回复2"], "swipes_id": 1}
|
||||||
Reference in New Issue
Block a user