from fastapi import FastAPI, HTTPException, Request, WebSocketDisconnect
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
import uvicorn
from server.generate.user_portrait_generate import *
from server.data.sensitiveWord_server import *
from server.chat_group.clientManager_server import *
from server.ai_api.base.base_feiying_service import *
from server.ai_api.base.base_doubao_service import *
from typing import AsyncGenerator
from contextlib import asynccontextmanager
from server.generate.story_generate import *
from fastapi.middleware.cors import CORSMiddleware
# 在初始化属性之前的函数,用于定时任务的制作
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# 在应用启动时执行的代码
# 启动定时生成 HTML 的任务
asyncio.create_task(all_periodic_html_generation())
asyncio.create_task(random_periodic_html_generation())
asyncio.create_task(all_treemap_html_generation())
asyncio.create_task(personality_update_story_generate_day())
# 应用启动后会在这里暂停,等待应用关闭
yield
# 函数后面是在应用关闭时执行的代码
# region 基础属性初始化生成
# 客户端管理器
clients_manager = ClientsManager()
# 消息存储,使用字典来存储群组的消息
message_store = []
# 全局知识图谱实例
girl_knowledge_graph = MemoryKnowledgeGraph()
# 过滤词系统
filter_system = SensitiveWordFilter(serialized_path="sensitive_filter.pkl")
# FastAPI
# 创建 FastAPI 实例并传入 lifespan
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有头
)
# 可信的记忆
cached_memories = set()
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/memory/using", StaticFiles(directory="memory/using"), name="memory_using")
# 获取单独一次的girl_setting
system_content = ""
# 用于发送给UE的聊天历史
chat_history_to_ue = []
# 语音识别文本模型
audio_to_text_model_path = "model/vosk-model-small-cn-0.22/"
# 音频保存位置
audio_save_file_path = "uploads/audio"
ai_model_name = ""
ai_api_key = ""
deepseek_api = ""
# 飞影设置
YOUR_TOKEN = ""
VOICE_ID = ""
AVATAR_ID = ""
SHOW_Subtitle = ""
num = 0
remember_me_update_state = 0
# 虚拟市民图像保存
virtual_cities_only_save_image_path = 'memory/using/virtual_image.png'
# 生成的讲故事视频的链接
remember_me_video_url = ""
global_task_id = ""
# 豆包的API key
dou_bao_api_key = ""
app_port = 10808
# 人设记录
current_user_profile = ""
# 是否更新 全部
all_video_update_state = 0
# 人设更新系统
# personality_system = None
# endregion
def begin_init():
global system_content,ai_model_name,ai_api_key, deepseek_api, app_port # 声明使用全局变量
global YOUR_TOKEN, VOICE_ID, AVATAR_ID, SHOW_Subtitle, dou_bao_api_key
# 人设加载
file_path = r"memory\GirlSetting.txt"
girl_config_path = r"memory\Girl_config.txt"
with open(girl_config_path, "r", encoding="utf-8") as f:
for line in f:
if "AI_MODEL =" in line:
ai_model_name = line.split('"')[1]
print(f"AI_MODEL: {ai_model_name}")
elif "AI_API_KEY =" in line:
ai_api_key = line.split('"')[1]
print(f"AI_API_KEY: {ai_api_key}")
elif "Deepseek_API =" in line:
deepseek_api = line.split('"')[1]
print(f"Deepseek_API: {deepseek_api}")
elif "FeiYing_YOUR_TOKEN =" in line:
YOUR_TOKEN = line.split('"')[1]
print(f"FeiYing_Token: {YOUR_TOKEN}")
elif "FeiYing_VOICE_ID =" in line:
VOICE_ID = line.split('"')[1]
print(f"FeiYing_Voice ID: {VOICE_ID}")
elif "FeiYing_AVATAR_ID =" in line:
AVATAR_ID = line.split('"')[1]
print(f"Avatar ID: {AVATAR_ID}")
elif "FeiYing_SHOW_Subtitle =" in line:
SHOW_Subtitle = int(line.split("=")[1].strip())
print(f"FeiYing_SHOW_Subtitle: {SHOW_Subtitle}")
elif "DouBao_API_KEY =" in line:
dou_bao_api_key = line.split('"')[1]
print(f"DouBao_API_KEY: {dou_bao_api_key}")
elif "APP_Port =" in line:
app_port = int(line.split("=")[1].strip())
print(f"APP_Port: {app_port}")
with open(file_path, "r", encoding="utf-8") as file:
system_content = file.read().strip()
# 知识图谱记忆库加载
girl_knowledge_graph.load_graph("girl_knowledge_graph.graphml")
# 初始化
begin_init()
# 创建服务实例
service = GirlZhipuAIService(ai_api_key, system_content, ai_model_name)
dou_bao_service = BaseDoubaoService(api_key=dou_bao_api_key)
# API检测
def check_api_key(api_key: str):
if api_key != "88888888":
return HTTPException(status_code=401, detail="无效的API密钥")
# 请求写法:http://127.0.0.1:8888/chat_without_memory?api_key=88888888 json:{message:xxx}
@app.post("/chat_with_memory")
async def chat_with_memory(request: Request, api_key: str = ''):
check_api_key(api_key)
data = await request.json()
user_input = data.get("message", "")
if not user_input:
raise HTTPException(status_code=400, detail="请求内容不能为空")
# 使用记忆库
temp_memory = girl_knowledge_graph.search_memory(user_input)
retries = 0
max_retries = 3
while retries < max_retries:
reply = service.chat_graph_knowledge(user_input, temp_memory, True)
# 不修改记忆库
result = girl_knowledge_graph.parse_and_store_response(reply,False)
if result:
return {"response": result}
retries += 1
return {"response": "emmm...我得仔细思考一下你说的问题,因为我现在脑子比较混乱"}
@app.post("/chat_without_memory")
async def chat_without_memory(request: Request, api_key: str = ''):
check_api_key(api_key)
data = await request.json()
user_input = data.get("message", "")
if not user_input:
raise HTTPException(status_code=400, detail="请求内容不能为空")
# 不使用记忆库
retries = 0
max_retries = 3
while retries < max_retries:
# 不使用记忆库
reply = service.chat_graph_knowledge(user_input, "", False,False)
result = girl_knowledge_graph.parse_and_store_response(reply,False)
if result:
return {"response": result}
retries += 1
return {"response": "emmm...我得仔细思考一下你说的问题,因为我现在脑子比较混乱"}
@app.get("/all_memory", response_class=HTMLResponse)
async def show_girl_graph(request: Request):
file_path = 'static/All_Girl_Graph.html'
if os.path.exists(file_path):
# 如果文件存在,读取并返回文件内容
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
return HTMLResponse(content=content)
else:
return "404"
@app.get("/memory", response_class=HTMLResponse)
async def show_girl_graph(request: Request):
file_path = 'static/Random_Girl_Graph.html'
if os.path.exists(file_path):
# 如果文件存在,读取并返回文件内容
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
return HTMLResponse(content=content)
else:
return "404"
@app.get("/remember_me_not_save_me/treemap_memory", response_class=HTMLResponse)
async def show_girl_graph(request: Request):
file_path = 'static/Treemap_Girl_Graph.html'
if os.path.exists(file_path):
# 如果文件存在,读取并返回文件内容
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
return HTMLResponse(content=content)
else:
return "404"
@app.post("/get_app_history")
async def get_app_history():
if chat_history_to_ue:
# 获取历史记录
history = chat_history_to_ue.copy()
# 清空历史记录
chat_history_to_ue.clear()
return history
else:
return ""
@app.post("/get_credible_memory")
async def get_credible_memory(request: Request):
# Unreal 的残留用代码
global cached_memories
data = await request.body()
temp_data = data.decode("utf-8")
print(temp_data)
# 从知识图谱中获取“认为可信的记忆”
new_memories = girl_knowledge_graph.get_memories_connections("认为可信的记忆")
# 转换为集合以便处理
new_memories_set = set(new_memories.split("|"))
if temp_data == "First":
print("First")
if not new_memories_set:
return ""
return "|".join(new_memories_set)
else:
print("NoFirst")
# 计算新增的记忆
unique_memories = new_memories_set - cached_memories
# 更新已缓存的记忆
cached_memories.update(unique_memories)
if not unique_memories:
return ""
return "|".join(unique_memories)
@app.post("/remember_me_not_save_me/get_user_portrait")
async def get_user_portrait():
base_profile_path = "memory/using/user_portrait.txt"
with open(base_profile_path, "r", encoding="utf-8") as f:
content= f.read()
print(content)
return content
@app.get("/remember_me_not_save_me/get_video_update_state")
async def get_video_update_state():
global remember_me_update_state,YOUR_TOKEN,global_task_id,remember_me_video_url, all_video_update_state
video_url = check_video_status(YOUR_TOKEN, global_task_id)
if video_url in ["1", "2", "4"]:
return JSONResponse({
"update": all_video_update_state,
"generate": 0
})
else:
remember_me_video_url = video_url
remember_me_update_state = 1
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = f"{timestamp}:{video_url}\n"
# 追加写入到文件(如果文件不存在会自动创建)
with open("memory/video_link_records.txt", "a", encoding="utf-8") as file:
file.write(record)
return JSONResponse({
"update": all_video_update_state,
"generate": remember_me_update_state
})
@app.get("/remember_me_not_save_me/get_video_link")
async def get_video_url():
global remember_me_video_url,remember_me_update_state, all_video_update_state
remember_me_update_state = 0
all_video_update_state = 0
return remember_me_video_url
@app.websocket("/connect_server")
async def websocket_endpoint(websocket: WebSocket):
# 接受 WebSocket 连接
await websocket.accept()
# 记录客户端连接
await clients_manager.connect(websocket)
try:
while True:
# 等待客户端发送消息
message = await websocket.receive_text()
# 判断消息类型:如果是 Base64 编码的图片
if message.startswith("IMAGE_MESSAGE:"):
# 处理 Base64 图片
image_data = message[len("IMAGE_MESSAGE:"):].strip()
image_path = await save_image_from_base64(image_data)
# 将保存的图片路径广播给其他客户端
await clients_manager.broadcast(f"Image received: {image_path}", sender=websocket, all_return=False)
append_to_log("User", image_path)
asyncio.create_task(add_memory_image(image_path))
elif message.startswith("USER_MESSAGE:"):
# 广播消息给除了自己以外的所有客户端
user_message = message[len("USER_MESSAGE:"):].strip()
user_message = filter_system.process_text(user_message)
await clients_manager.broadcast(user_message, sender=websocket, all_return=False)
message_store.append({"message": user_message, "sender": "user"})
# 写入本地
append_to_log("User", user_message)
asyncio.create_task(add_memory_str(user_message))
else:
append_to_log("System", "Unknown message type received")
# 启动一个定时任务来检查消息是否已经超过 5 秒
asyncio.create_task(check_message_timeout())
except WebSocketDisconnect:
# 如果客户端断开连接,则从管理器中删除该连接
await clients_manager.disconnect(websocket)
async def all_periodic_html_generation():
while True:
# 生成 HTML
girl_knowledge_graph.generate_graph_dynamic_html('static/All_Girl_Graph.html')
# 去重 - 0.8
girl_knowledge_graph.remove_similar_nodes_distance(0.8)
# 每60秒执行一次
await asyncio.sleep(300)
async def random_periodic_html_generation():
while True:
# 生成 HTML
girl_knowledge_graph.random_Generate_dynamic_html('static/Random_Girl_Graph.html')
# 每20秒执行一次
await asyncio.sleep(20)
async def all_treemap_html_generation():
while True:
# 生成 HTML
result = girl_knowledge_graph.get_recent_memories(days=500)
categorized = girl_knowledge_graph.categorize_memories_by_type(result)
girl_knowledge_graph.generate_fullscreen_treemap_html(categorized, 'static/Treemap_Girl_Graph.html')
# 每10分钟执行一次
await asyncio.sleep(600)
async def personality_update_story_generate_day():
# 一整个更新视频流程
global virtual_cities_only_save_image_path, num, current_user_profile, all_video_update_state
global YOUR_TOKEN, VOICE_ID, AVATAR_ID, SHOW_Subtitle
global global_task_id, deepseek_api
if num !=0:
# 更新人设
current_girl_system_content = service.system_girl
# 记录历史 --> 用于回滚过去人设
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_file_path = f"memory/personality_history/GirlSetting_{timestamp}.txt"
os.makedirs(os.path.dirname(save_file_path), exist_ok=True)
write_to_txt(save_file_path,current_girl_system_content)
# 生成新的人设 并 修改
recent_memory = girl_knowledge_graph.get_recent_memories(days=1)
new_setting = service.chat_update_girl_setting(current_girl_system_content,recent_memory)
write_to_txt("memory/GirlSetting.txt",new_setting)
service.system_girl = new_setting
print("新人设生成修改完毕")
# 基于新的记忆生成新的用户画像-->"memory/using/user_portrait.txt"
new_profile = merge_profiles_with_deepseek(service=service, deepseek_api=deepseek_api, days=0.5, log_path="log.txt", new_setting=new_setting, recent_user_profile=current_user_profile)
current_user_profile = new_profile
# 生成新的画图提示词
image_prompt = "基于这个用户画像与人设去生成一个专门用于绘画的json提示词,画一个大家心目中的人物,正面人脸,这个是用户画像,超写实:" + new_profile + "\n人设:" + new_setting
user_portrait_prompt = service.chat(image_prompt)
# 画图提示词-->生成图
""" # chatglm画图
image_url = service.generate_image(
prompt=user_portrait_prompt,
model="cogview-3-flash",
size="1024x1024",
watermark_enabled=False # 去水印
)"""
image_url = dou_bao_service.generate_image_and_get_url(
prompt=user_portrait_prompt, watermark=False
)
# 目录存在
os.makedirs("memory/virtual_image", exist_ok=True)
# 保存位置
image_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
time_save_path = f'memory/virtual_image/VI_{image_timestamp}.png'
response = requests.get(image_url, stream=True)
# 下载 + 保存图片
image_data = b""
for chunk in response.iter_content(chunk_size=8192):
image_data += chunk
with open(virtual_cities_only_save_image_path, 'wb') as file:
file.write(image_data)
# 保存到带时间戳的路径(历史记录)
with open(time_save_path, 'wb') as file:
file.write(image_data)
# 故事生成 -> "memory/using/merged_stories.txt"
final_merged_story = await merge_stories_with_deepseek(deepseek_api=deepseek_api, service=service,open_log_path="log.txt", knowledge_graph=girl_knowledge_graph, recent_user_profile= current_user_profile)
# 将图片转为视频
# generate_video_from_image(used_image_path, "static/video/loop_video.mp4", 5)
# 创建视频生成任务
text_prompt = "电影感特写镜头,固定机位。人物凝视镜头,缓慢地眨一次眼,随后做一个微小的自然动作(例如:嘴角浅笑),然后恢复初始的平静表情。动态微妙,镜头稳定,风格化画面,无缝循环。"
# 虚拟形象图片
image_url = "http://150.158.170.63:" + str(app_port) + "/" + virtual_cities_only_save_image_path
video_task = dou_bao_service.create_video_generation_task(
text_prompt=text_prompt,
image_url=image_url,
watermark=False
)
task_id = video_task.get("id")
if task_id:
# 等待任务完成
video_url, status = dou_bao_service.wait_for_video_task(task_id)
if status == "succeeded":
print("视频生成成功,URL:", video_url)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
video_record = f"{timestamp}:{video_url}"
os.makedirs(os.path.dirname(save_file_path), exist_ok=True)
with open("memory/dou_bao_no_voice_video.txt", "a", encoding="utf-8") as file:
file.write(video_record)
# 下载并覆盖视频
await dou_bao_service.download_file(video_url, "static/video/loop_video.mp4")
# 上传飞影视频
avatar_id = create_avatar_from_local_video_back_avatar_id(token=YOUR_TOKEN, video_path="static/video/loop_video.mp4", title="test")
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = f"{timestamp}:{avatar_id}\n"
# 追加写入到文件(如果文件不存在会自动创建)
with open("memory/avatar_id_records.txt", "a", encoding="utf-8") as file:
file.write(record)
result = create_feiying_video_by_tts(
token=YOUR_TOKEN,
voice_id=VOICE_ID,
avatar_id=avatar_id,
text=final_merged_story,
show_subtitle=SHOW_Subtitle
)
global_task_id = result.get("task_id")
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = f"{timestamp}:https://hfw-api.hifly.cc/api/v2/hifly/video/task?task_id={global_task_id}\n"
# 追加写入到文件(如果文件不存在会自动创建)
with open("memory/video_task_records.txt", "a", encoding="utf-8") as file:
file.write(record)
all_video_update_state = 1
elif num == 0:
num = 1
# 每1个小时生成一次新的人设表、故事、用户画像、飞影视频
await asyncio.sleep(3600)
# 定时检查是否有新的消息,如果没有消息,则调用 AI 生成回复
async def check_message_timeout():
# 等待 5 秒钟
await asyncio.sleep(5)
# 检查当前消息列表
if len(message_store) > 0:
# 随机选择一条消息
selected_message = random.choice(message_store)
question = selected_message["message"]
# 调用 AI 获取回复
ai_response = await ai_chat(question)
append_to_log("AI", ai_response)
# 广播消息给所有客户端
await clients_manager.broadcast(ai_response, sender="ai", all_return=True)
chat_history_to_ue.append({"question": question, "answer": ai_response})
# 清空消息存储
message_store.clear()
async def ai_chat(player_message: str):
# 检索回忆
temp_memory = girl_knowledge_graph.search_memory(player_message)
retries = 0
max_retries = 3
while retries < max_retries:
# 请求聊天生成的回答
reply = service.chat_graph_knowledge(player_message, temp_memory, True)
# 解析并存储响应
result = girl_knowledge_graph.parse_and_store_response(reply)
if result: # 如果解析成功,返回结果
return result
else:
retries += 1
if retries>= max_retries:
return """emmm...我得仔细思考一下你说的问题,因为我现在脑子比较混乱"""
async def add_memory_str(new_memory: str):
memory_text = service.chat_graph_knowledge(new_memory, "", False)
girl_knowledge_graph.parse_and_store_response(memory_text)
async def add_memory_image(file_path: str):
res = service.chat_image_solve(file_path)
answer = """这个是别人给你发来照片后,你自己对照片的描述:""" + res + """\n请你根据人设和相关回忆设定回答"""
message_store.append({"message": answer, "sender": "user"})
# 存入记忆
memory_text = service.chat_graph_knowledge(res, "", False)
girl_knowledge_graph.parse_and_store_response(memory_text)
async def audio_get():
wav_path = await asyncio.to_thread(record_audio_manual_stop, "output/audio_files")
return wav_path
async def audio_to_text(audio_file_path: str):
audio_text = transcribe_audio(audio_to_text_model_path,audio_file_path=audio_file_path)
return audio_text
if __name__ == '__main__':
# a = asyncio.run(audio_to_text("C:\\Users\\ADMIN\\Desktop\\声音\\小黑塔\\小黑塔的初次见面语音.wav"))
# print(a)
# merge_stories_with_deepseek() # 用于自己使用
# asyncio.run(personality_update_story_generate_day()) # 人设重新设置和故事生成
uvicorn.run(app, host='0.0.0.0', port=app_port, log_level="info")
# asyncio.run(t_2())
打包后的python文件,打开报错:
Error in sys.excepthook:
Original exception was:
Error in sys.excepthook!
Original exception was: