Compare commits

..

4 Commits

Author SHA1 Message Date
2984aaa4be 修改崩溃bug 2025-07-10 09:30:45 +08:00
873dd45584 修改崩溃bug 2025-07-10 09:30:32 +08:00
d1326b7776 修复服务器bug 2025-07-09 19:58:44 +08:00
23b62b60a5 完善对话生成逻辑 2025-07-09 14:55:51 +08:00
17 changed files with 441 additions and 278 deletions

View File

@ -1,17 +1,22 @@
import logging
from typing import Tuple
import requests import requests
from ollama import Client, ResponseError from ollama import Client, ResponseError
import tiktoken import tiktoken
import random
from Utils.AIGCLog import AIGCLog
class AICore: class AICore:
modelMaxTokens = 128000 modelMaxTokens = 128000
# 初始化 DeepSeek 使用的 Tokenizer (cl100k_base) # 初始化 DeepSeek 使用的 Tokenizer (cl100k_base)
encoder = tiktoken.get_encoding("cl100k_base") encoder = tiktoken.get_encoding("cl100k_base")
logger = AIGCLog(name = "AIGC", log_file = "aigc.log")
def __init__(self, model): def __init__(self, model):
#初始化ollama客户端 #初始化ollama客户端
self.ollamaClient = Client(host='http://localhost:11434', headers={'x-some-header': 'some-value'}) self.ollamaClient = Client(host='http://localhost:11434', headers={'x-some-header': 'some-value'})
self.modelName = model
response = self.ollamaClient.show(model) response = self.ollamaClient.show(model)
modelMaxTokens = response.modelinfo['qwen2.context_length'] modelMaxTokens = response.modelinfo['qwen2.context_length']
@ -19,3 +24,24 @@ class AICore:
tokens = self.encoder.encode(prompt) tokens = self.encoder.encode(prompt)
return len(tokens) return len(tokens)
def generateAI(self, promptStr: str) -> Tuple[bool, str]:
try:
response = self.ollamaClient.generate(
model = self.modelName,
stream = False,
prompt = promptStr,
options={
"temperature": random.uniform(1.0, 1.5),
"repeat_penalty": 1.2, # 抑制重复
"top_p": random.uniform(0.7, 0.95),
"num_ctx": 4096, # 上下文长度
}
)
return True, response.response
except ResponseError as e:
if e.status_code == 503:
print("🔄 服务不可用5秒后重试...")
return False,"ollama 服务不可用"
except Exception as e:
print(f"🔥 未预料错误: {str(e)}")
return False, "未预料错误"

View File

@ -103,17 +103,18 @@ class DatabaseHandle:
conn.commit() conn.commit()
return cursor.lastrowid return cursor.lastrowid
def get_chats_by_character_id(self, character_id: int) -> list: def get_chats_by_character_id(self, character_id: str) -> list:
""" """
根据角色ID查询聊天记录target_id为空时返回全部数据 根据角色ID查询聊天记录target_id为空时返回全部数据
:param target_id: 目标角色IDNone时返回全部记录 :param target_id: 目标角色IDNone时返回全部记录
:return: 聊天记录字典列表 :return: 聊天记录字典列表
""" """
sorted_ids = sorted(character_id.split(","), key=int) # 按数值升序
normalized_param = ",".join(sorted_ids)
with self._get_connection() as conn: with self._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
sql = "SELECT * FROM chat_records WHERE ',' || character_ids || ',' LIKE '%,' || ? || ',%'" sql = "SELECT * FROM chat_records WHERE character_ids = ?"
params = (str(character_id)) cursor.execute(sql, (normalized_param,))
cursor.execute(sql, params)
# 转换结果为字典列表
columns = [col[0] for col in cursor.description] columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()] return [dict(zip(columns, row)) for row in cursor.fetchall()]

View File

@ -2,7 +2,7 @@
chcp 65001 > nul chcp 65001 > nul
set OLLAMA_MODEL=deepseek-r1:7b set OLLAMA_MODEL=deepseek-r1:7b
rem 启动Ollama服务 rem 启动Ollama服务
start "Ollama DeepSeek" cmd /k ollama run %OLLAMA_MODEL% start "Ollama DeepSeek" cmd /k ollama serve
rem 检测11434端口是否就绪 rem 检测11434端口是否就绪
echo 等待Ollama服务启动... echo 等待Ollama服务启动...

Binary file not shown.

View File

@ -26,11 +26,15 @@ parser.add_argument('--model', type=str, default='deepseek-r1:1.5b',
args = parser.parse_args() args = parser.parse_args()
logger.log(logging.INFO, f"使用的模型是 {args.model}") logger.log(logging.INFO, f"使用的模型是 {args.model}")
maxAIRegerateCount = 5 maxAIRegerateCount = 5 #最大重新生成次数
regenerateCount = 1 #当前重新生成次数
totalAIGenerateCount = 1 #客户端生成AI响应总数
currentGenerateCount = 0 #当前生成次数
lastPrompt = "" lastPrompt = ""
character_id1 = 0
character_id2 = 0
aicore = AICore(args.model) aicore = AICore(args.model)
database = DatabaseHandle()
async def heartbeat(websocket: WebSocket): async def heartbeat(websocket: WebSocket):
pass pass
@ -48,6 +52,18 @@ async def senddata(websocket: WebSocket, protocol: dict):
json_string = json.dumps(protocol, ensure_ascii=False) json_string = json.dumps(protocol, ensure_ascii=False)
await websocket.send_text(json_string) await websocket.send_text(json_string)
async def sendprotocol(websocket: WebSocket, cmd: str, status: int, message: str, data: str):
# 将AI响应发送回UE5
protocol = {}
protocol["cmd"] = cmd
protocol["status"] = status
protocol["message"] = message
protocol["data"] = data
if websocket.client_state == WebSocketState.CONNECTED:
json_string = json.dumps(protocol, ensure_ascii=False)
await websocket.send_text(json_string)
# WebSocket路由处理 # WebSocket路由处理
@app.websocket("/ws/{client_id}") @app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str): async def websocket_endpoint(websocket: WebSocket, client_id: str):
@ -63,19 +79,6 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
logger.log(logging.INFO, f"收到UE5消息 [{client_id}]: {data}") logger.log(logging.INFO, f"收到UE5消息 [{client_id}]: {data}")
await process_protocol_json(data, websocket) await process_protocol_json(data, websocket)
# success, prompt = process_prompt(data)
# global lastPrompt
# lastPrompt = prompt
# # 调用AI生成响应
# if(success):
# asyncio.create_task(generateAIChat(prompt, websocket))
# await senddata(websocket, 0, [])
# else:
# await senddata(websocket, -1, [])
except WebSocketDisconnect: except WebSocketDisconnect:
#manager.disconnect(client_id) #manager.disconnect(client_id)
logger.log(logging.WARNING, f"UE5客户端主动断开 [{client_id}]") logger.log(logging.WARNING, f"UE5客户端主动断开 [{client_id}]")
@ -87,7 +90,7 @@ async def handle_characterlist(client: WebSocket):
### 获得数据库中的角色信息### ### 获得数据库中的角色信息###
characters = database.get_character_byname("") characters = database.get_character_byname("")
protocol = {} protocol = {}
protocol["cmd"] = "CharacterList" protocol["cmd"] = "RequestCharacterInfos"
protocol["status"] = 1 protocol["status"] = 1
protocol["message"] = "success" protocol["message"] = "success"
characterforUE = {} characterforUE = {}
@ -95,6 +98,84 @@ async def handle_characterlist(client: WebSocket):
protocol["data"] = json.dumps(characterforUE) protocol["data"] = json.dumps(characterforUE)
await senddata(client, protocol) await senddata(client, protocol)
async def handle_characternames(client: WebSocket):
### 获得数据库中的角色信息###
characters = database.get_character_byname("")
protocol = {}
protocol["cmd"] = "RequestCharacterNames"
protocol["status"] = 1
protocol["message"] = "success"
characterforUE = {}
characterforUE["characterInfos"] = characters
protocol["data"] = json.dumps(characterforUE)
await senddata(client, protocol)
async def generate_aichat(promptStr: str, client: WebSocket| None = None):
dynamic_token = str(int(time.time() % 1000))
promptStr = f"""
[动态标识码:{dynamic_token}]
""" + promptStr
logger.log(logging.INFO, "prompt:" + promptStr)
starttime = time.time()
success, response, = aicore.generateAI(promptStr)
if(success):
logger.log(logging.INFO, "接口调用耗时 :" + str(time.time() - starttime))
logger.log(logging.INFO, "AI生成" + response)
#处理ai输出内容
think_remove_text = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
pattern = r".*<format>(.*?)</format>" # .* 吞掉前面所有字符,定位最后一组
match = re.search(pattern, think_remove_text, re.DOTALL)
if not match:
#生成内容格式错误
if await reGenerateAIChat(lastPrompt, client):
pass
else:
#超过重新生成次数
logger.log(logging.ERROR, "请更换prompt,或者升级模型大小")
await sendprotocol(client, "AiChatGenerate", 0, "请更换prompt,或者升级模型大小", "")
else:
#生成内容格式正确
core_dialog = match.group(1).strip()
dialog_lines = [line.strip() for line in core_dialog.split('\n') if line.strip()]
if len(dialog_lines) != 4:
#生成内容格式错误
if await reGenerateAIChat(lastPrompt, client):
pass
else:
logger.log(logging.ERROR, "请更换prompt或者升级模型大小")
await sendprotocol(client, "AiChatGenerate", 0, "请更换prompt或者升级模型大小", "")
else:
logger.log(logging.INFO, "AI的输出正确\n" + core_dialog)
global regenerateCount
regenerateCount = 0
#保存数据到数据库
database.add_chat({"character_ids":f"{character_id1},{character_id2}","chat":f"{" ".join(dialog_lines)}"})
await sendprotocol(client, "AiChatGenerate", 1, "AI生成成功", "|".join(dialog_lines))
else:
await sendprotocol(client, "AiChatGenerate", -1, "调用ollama服务失败", "")
async def handle_aichat_generate(client: WebSocket, aichat_data:str):
### 处理ai prompt###
success, prompt = process_prompt(aichat_data)
global lastPrompt
lastPrompt = prompt
# 调用AI生成响应
if(success):
#asyncio.create_task(generateAIChat(prompt, client))
global currentGenerateCount
while currentGenerateCount < totalAIGenerateCount:
currentGenerateCount += 1
await generate_aichat(prompt, client)
currentGenerateCount = 0
#全部生成完成
await sendprotocol(client, "AiChatGenerate", 2, "AI生成成功", "")
else:
#prompt生成失败
await sendprotocol(client, "AiChatGenerate", -1, "prompt convert failed", "")
async def handle_addcharacter(client: WebSocket, chracterJson: str): async def handle_addcharacter(client: WebSocket, chracterJson: str):
### 添加角色到数据库 ### ### 添加角色到数据库 ###
character_info = json.loads(chracterJson) character_info = json.loads(chracterJson)
@ -109,10 +190,14 @@ async def process_protocol_json(json_str: str, client: WebSocket):
protocol = json.loads(json_str) protocol = json.loads(json_str)
cmd = protocol.get("cmd") cmd = protocol.get("cmd")
data = protocol.get("data") data = protocol.get("data")
if cmd == "CharacterList": if cmd == "RequestCharacterInfos":
await handle_characterlist(client) await handle_characterlist(client)
elif cmd == "RequestCharacterNames":
await handle_characternames(client)
elif cmd == "AddCharacter": elif cmd == "AddCharacter":
await handle_addcharacter(client, data) await handle_addcharacter(client, data)
elif cmd == "AiChatGenerate":
await handle_aichat_generate(client, data)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}") print(f"JSON解析错误: {e}")
@ -121,42 +206,65 @@ async def process_protocol_json(json_str: str, client: WebSocket):
def process_prompt(promptFromUE: str) -> Tuple[bool, str]: def process_prompt(promptFromUE: str) -> Tuple[bool, str]:
try: try:
data = json.loads(promptFromUE) data = json.loads(promptFromUE)
global maxAIRegerateCount
# 提取数据 # 提取数据
dialog_scene = data["dialogContent"]["dialogScene"] dialog_scene = data["dialogScene"]
persons = data["persons"] global totalAIGenerateCount
totalAIGenerateCount = data["generateCount"]
persons = data["characterName"]
assert len(persons) == 2 assert len(persons) == 2
for person in persons: characterInfo1 = database.get_character_byname(persons[0])
print(f" 姓名: {person['name']}, 职业: {person['job']}") characterInfo2 = database.get_character_byname(persons[1])
global character_id1, character_id2
character_id1 = characterInfo1[0]["id"]
character_id2 = characterInfo2[0]["id"]
chat_history = database.get_chats_by_character_id(str(character_id1) + "," + str(character_id2))
#整理对话记录
result = result = '\n'.join([item['chat'] for item in chat_history])
prompt = f""" prompt = f"""
你是一个游戏NPC对话生成器请严格按以下要求生成两个路人NPC{persons[0]["name"]}{persons[1]["name"]}的日常对话 #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
1. 生成2轮完整对话每轮包含双方各一次发言共4句 #对话的背景
2. 对话场景{dialog_scene} {dialog_scene}
3. 角色设定 1. 生成2轮完整对话每轮包含双方各一次发言共4句
{persons[0]["name"]}{persons[0]["job"]} 2.角色设定
{persons[1]["name"]}{persons[1]["job"]} {characterInfo1[0]["name"]}: {{
4. 对话要求 "姓名": {characterInfo1[0]["name"]},
* 每轮对话需自然衔接体现生活细节 "年龄": {characterInfo1[0]["age"]},
* 避免任务指引或玩家交互内容 "性格": {characterInfo1[0]["personality"]},
* 结尾保持对话未完成感 "职业": {characterInfo1[0]["profession"]},
5. 输出格式 "背景": {characterInfo1[0]["characterBackground"]},
<format> "语言风格": {characterInfo1[0]["chat_style"]}
{persons[0]["name"]}[第一轮发言] }},
{persons[1]["name"]}[第一轮回应] {characterInfo2[0]["name"]}: {{
{persons[0]["name"]}[第二轮发言] "姓名": {characterInfo2[0]["name"]},
{persons[1]["name"]}[第二轮回应] "年龄": {characterInfo2[0]["age"]},
</format> "性格": {characterInfo2[0]["personality"]},
"职业": {characterInfo2[0]["profession"]},
6.重要若未按此格式输出请重新生成直至完全符合 "背景": {characterInfo2[0]["characterBackground"]},
"语言风格": {characterInfo2[0]["chat_style"]}
}}
3.参考的历史对话内容
{result}
4.输出格式
<format>
张三[第一轮发言]
李明[第一轮回应]
张三[第二轮发言]
李明[第二轮回应]
</format>
5.重要若未按此格式输出请重新生成直至完全符合
""" """
return True, prompt return True, prompt
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}") print(f"JSON解析错误: {e}")
return False, "" return False, ""
except KeyError as e: except Exception as e:
print(f"缺少必要字段: {e}") print(f"发生错误:{type(e).__name__} - {e}")
return False, ""
@ -173,91 +281,15 @@ def run_webserver():
log_level="info" log_level="info"
) )
async def generateAIChat(promptStr: str, websocket: WebSocket| None = None):
#动态标识吗 防止重复输入导致的结果重复
dynamic_token = str(int(time.time() % 1000))
promptStr = f"""
[动态标识码:{dynamic_token}]
""" + promptStr
logger.log(logging.INFO, "prompt:" + promptStr)
starttime = time.time()
receivemessage=[
{"role": "system", "content": promptStr}
]
try:
# response = ollamaClient.chat(
# model = args.model,
# stream = False,
# messages = receivemessage,
# options={
# "temperature": random.uniform(1.0, 1.5),
# "repeat_penalty": 1.2, # 抑制重复
# "top_p": random.uniform(0.7, 0.95),
# "num_ctx": 4096, # 上下文长度
# "seed": int(time.time() * 1000) % 1000000
# }
# )
response = ollamaClient.generate(
model = args.model,
stream = False,
prompt = promptStr,
options={
"temperature": random.uniform(1.0, 1.5),
"repeat_penalty": 1.2, # 抑制重复
"top_p": random.uniform(0.7, 0.95),
"num_ctx": 4096, # 上下文长度
}
)
except ResponseError as e:
if e.status_code == 503:
print("🔄 服务不可用5秒后重试...")
return await senddata(websocket, -1, messages=["ollama 服务不可用"])
except Exception as e:
print(f"🔥 未预料错误: {str(e)}")
return await senddata(websocket, -1, messages=["未预料错误"])
logger.log(logging.INFO, "接口调用耗时 :" + str(time.time() - starttime))
#aiResponse = response['message']['content']
aiResponse = response['response']
logger.log(logging.INFO, "AI生成" + aiResponse)
#处理ai输出内容
think_remove_text = re.sub(r'<think>.*?</think>', '', aiResponse, flags=re.DOTALL)
pattern = r".*<format>(.*?)</format>" # .* 吞掉前面所有字符,定位最后一组
match = re.search(pattern, think_remove_text, re.DOTALL)
if not match:
if await reGenerateAIChat(lastPrompt, websocket):
pass
else:
logger.log(logging.ERROR, "请更换prompt或者升级模型大小")
await senddata(websocket, -1, messages=["请更换prompt或者升级模型大小"])
else:
core_dialog = match.group(1).strip()
dialog_lines = [line for line in core_dialog.split('\n') if line.strip()]
if len(dialog_lines) != 4:
if await reGenerateAIChat(lastPrompt, websocket):
pass
else:
logger.log(logging.ERROR, "请更换prompt或者升级模型大小")
await senddata(websocket, -1, messages=["请更换prompt或者升级模型大小"])
else:
logger.log(logging.INFO, "AI的输出正确\n" + core_dialog)
global regenerateCount
regenerateCount = 0
await senddata(websocket, 1, dialog_lines)
regenerateCount = 1
async def reGenerateAIChat(prompt: str, websocket: WebSocket): async def reGenerateAIChat(prompt: str, websocket: WebSocket):
global regenerateCount global regenerateCount
if regenerateCount < maxAIRegerateCount: if regenerateCount < maxAIRegerateCount:
regenerateCount += 1 regenerateCount += 1
logger.log(logging.ERROR, f"AI输出格式不正确重新进行生成 {regenerateCount}/{maxAIRegerateCount}") logger.log(logging.ERROR, f"AI输出格式不正确重新进行生成 {regenerateCount}/{maxAIRegerateCount}")
await senddata(websocket, 2, messages=["ai生成格式不正确 重新进行生成"]) await sendprotocol(websocket, "AiChatGenerate", 0, "ai生成格式不正确 重新进行生成", "")
await asyncio.sleep(0) await asyncio.sleep(0)
prompt = prompt + "补充上一次的输出格式错误严格执行prompt中第5条的输出格式要求" prompt = prompt + "补充上一次的输出格式错误严格执行prompt中的输出格式要求"
await generateAIChat(prompt, websocket) await generate_aichat(prompt, websocket)
return True return True
else: else:
regenerateCount = 0 regenerateCount = 0
@ -272,19 +304,18 @@ if __name__ == "__main__":
#Test database #Test database
database = DatabaseHandle()
id = database.add_character({"name":"李明","age":30,"personality":"活泼健谈","profession":"产品经理"
,"characterBackground":"公司资深产品经理","chat_style":"热情"})
characters = database.get_character_byname("") # id = database.add_character({"name":"李明","age":30,"personality":"活泼健谈","profession":"产品经理"
# ,"characterBackground":"公司资深产品经理","chat_style":"热情"})
#characters = database.get_character_byname("")
#chat_id = database.add_chat({"character_ids":"1,2","chat":"张三:[第一轮发言] 李明:[第一轮回应] 张三:[第二轮发言] 李明:[第二轮回应"}) #chat_id = database.add_chat({"character_ids":"1,2","chat":"张三:[第一轮发言] 李明:[第一轮回应] 张三:[第二轮发言] 李明:[第二轮回应"})
chat = database.get_chats_by_character_id(3) #chat = database.get_chats_by_character_id(3)
if id == 0: #
logger.log(logging.ERROR, f"角色 张三已经添加到数据库")
# Test AI # Test AI
aicore.getPromptToken("测试功能") #aicore.getPromptToken("测试功能")
# asyncio.run( # asyncio.run(
# generateAIChat(promptStr = f""" # generateAIChat(promptStr = f"""
# #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话 # #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话

Binary file not shown.

View File

@ -1,5 +1,6 @@
#include "Definations.h" #include "Definations.h"
FString FNetCommand::CharacterList = TEXT("CharacterList"); FString FNetCommand::RequestCharacterInfos = TEXT("RequestCharacterInfos");
FString FNetCommand::RequestCharacterNames = TEXT("RequestCharacterNames");
FString FNetCommand::AddCharacter = TEXT("AddCharacter"); FString FNetCommand::AddCharacter = TEXT("AddCharacter");
FString FNetCommand::AiChatGenerate = TEXT("AiChatGenerate"); FString FNetCommand::AiChatGenerate = TEXT("AiChatGenerate");

View File

@ -188,8 +188,19 @@ void FAIGCModule::InitWebSocketManager()
auto& Settings = *GetDefault<UAIGCSetting>(); auto& Settings = *GetDefault<UAIGCSetting>();
WebSocketManager = NewObject<UWebSocketManager>(); WebSocketManager = NewObject<UWebSocketManager>();
WebSocketManager->InitWebSocket(Settings.ServerIP); WebSocketManager->InitWebSocket(Settings.ServerIP);
WebSocketManager->OnConnectDelegate.AddLambda([this](bool bSuccess)
{
if (!bSuccess)
{
WebSocketManager->ConditionalBeginDestroy();
WebSocketManager = nullptr;
}
});
//WebSocketManager->OnConnectDelegate.AddRaw(this, &FAIGCModule::OnWebSocketConnect);
} }
#undef LOCTEXT_NAMESPACE #undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAIGCModule, AIGC) IMPLEMENT_MODULE(FAIGCModule, AIGC)

View File

@ -15,15 +15,6 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
{ {
ChildSlot[ ChildSlot[
SNew(SVerticalBox) SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(10)
[
SAssignNew(ServerIP, SConfigItem_Text)
.Title(LOCTEXT("Server", "服务器ip"))
.HintText(LOCTEXT("HintServer", "请输入服务器ip"))
.DefaultText(LOCTEXT("InputServer", "127.0.0.1"))
]
+ SVerticalBox::Slot() + SVerticalBox::Slot()
.AutoHeight() .AutoHeight()
.Padding(10) .Padding(10)
@ -36,35 +27,48 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
.AutoHeight() .AutoHeight()
.Padding(10) .Padding(10)
[ [
SAssignNew(NPCName1, SConfigItem_Text) SAssignNew(NPCName1, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
.Title(LOCTEXT("Name1", "名称1")) .Title(LOCTEXT("Name1", "名称1"))
.HintText(LOCTEXT("HintName", "请输入名称")) .OptionsSource(&CharacterInfos)
.Content()
[
SNew(STextBlock)
.Text_Lambda([this]() -> FText {
return FText::FromString(SelectedCharacter1.name);
})
]
.OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
{
SelectedCharacter1 = *CharacterInfo;
})
] ]
+ SVerticalBox::Slot() + SVerticalBox::Slot()
.AutoHeight() .AutoHeight()
.Padding(10) .Padding(10)
[ [
SAssignNew(NPCJob1, SConfigItem_Text) SAssignNew(NPCName2, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
.Title(LOCTEXT("Job1", "职业1")) .Title(LOCTEXT("Name2", "名称2"))
.HintText(LOCTEXT("HintJon", "请输入职业")) .OptionsSource(&CharacterInfos)
] .Content()
+ SVerticalBox::Slot() [
.AutoHeight() SNew(STextBlock)
.Padding(10) .Text_Lambda([this]() -> FText {
[
SAssignNew(NPCName2, SConfigItem_Text)
.Title(LOCTEXT("Name2", "名称2"))
.HintText(LOCTEXT("HintName", "请输入名称"))
]
+ SVerticalBox::Slot() return FText::FromString(SelectedCharacter2.name);
.AutoHeight() })
.Padding(10) ]
[ .OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
SAssignNew(NPCJob2, SConfigItem_Text) return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
.Title(LOCTEXT("Job2", "职业2")) })
.HintText(LOCTEXT("HintJon", "请输入职业")) .OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
] {
SelectedCharacter2 = *CharacterInfo;
})
]
+ SVerticalBox::Slot() + SVerticalBox::Slot()
.AutoHeight() .AutoHeight()
.Padding(10) .Padding(10)
@ -111,14 +115,19 @@ EActiveTimerReturnType SAIGCWindow::OnPostPaint(double X, float Arg)
if (ModulePtr) if (ModulePtr)
{ {
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager(); UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
if (!WebSocketManager) if (!IsValid(WebSocketManager))
{ {
ModulePtr->InitWebSocketManager(); ModulePtr->InitWebSocketManager();
WebSocketManager = ModulePtr->GetWebSocketManager(); WebSocketManager = ModulePtr->GetWebSocketManager();
} }
WebSocketManager->OnConnectDelegate.AddLambda([this](bool bSuccess) else
{
WebSocketManager->SendData(FNetCommand::RequestCharacterNames, TEXT(""));
}
WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess)
{ {
GenerateButton->SetEnabled(bSuccess); GenerateButton->SetEnabled(bSuccess);
WebSocketManager->SendData(FNetCommand::RequestCharacterNames, TEXT(""));
}); });
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData); WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData);
@ -131,11 +140,27 @@ void SAIGCWindow::OnAIGenerateClicked()
{ {
GenerateButton->SetEnabled(false); GenerateButton->SetEnabled(false);
RequireGenerateCount = GenerateCount->GetNumber(); RequireGenerateCount = GenerateCount->GetNumber();
UE_LOG(LogTemp, Warning, TEXT("生成次数 %d prompt:%s"), RequireGenerateCount, *GeneratePromptJson());
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC"); FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
//生成ai 配置信息
FRequestAIChat RequestAIChat;
RequestAIChat.DialogScene =DialogScene->GetInputText();
RequestAIChat.GenerateCount = GenerateCount->GetNumber();
RequestAIChat.CharacterName.Add(SelectedCharacter1.name);
RequestAIChat.CharacterName.Add(SelectedCharacter2.name);
FString dataJson;
FJsonObjectConverter::UStructToJsonObjectString(
FRequestAIChat::StaticStruct(),
&RequestAIChat,
dataJson,
0,
0
);
//发送ai对话生成请求
if (ModulePtr) if (ModulePtr)
{ {
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, GeneratePromptJson()); ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, dataJson);
} }
} }
@ -143,58 +168,53 @@ void SAIGCWindow::OnAIGenerateClicked()
void SAIGCWindow::HandleReceiveData(FNetProtocol protocol) void SAIGCWindow::HandleReceiveData(FNetProtocol protocol)
{ {
// UE_LOG(LogTemp, Warning, TEXT("AI当前进度 %d/%d"), GeneratedCount, RequireGenerateCount); if (protocol.cmd == FNetCommand::RequestCharacterNames)
// //添加数据到dataTable中 {
// if (AiMessages.Num() > 0)
// {
// FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
// if (ModulePtr) {
// FString DataPath = FString::Printf(TEXT("/Game/Test/%s.%s"), *DataName->GetInputText(), *DataName->GetInputText());
// FString Final = "";
// for (auto message: AiMessages)
// {
// Final += message + TEXT("|");
// }
// ModulePtr->CreateOrAddData(DataPath, Final);
// }
// }
//
// //是否生成足够数量
// if (GeneratedCount < RequireGenerateCount)
// {
// GeneratedCount++;
// FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
// if (ModulePtr)
// {
// ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, GeneratePromptJson());
// }
//
// }
// else
// {
// GeneratedCount = 0;
// GenerateButton->SetEnabled(true);
// UE_LOG(LogTemp, Warning, TEXT("生成结束!"));
// }
} //解析json角色信息
FJsonObjectConverter::JsonObjectStringToUStruct<FCharacterArray>(
protocol.data,
&Characters,
0, // 检查标志位
0 // 转换标志位
);
CharacterInfos.Empty();
for (auto Character: Characters.characterInfos)
{
CharacterInfos.Add(MakeShareable(new FCharacterInfo(Character)));
}
NPCName1->RefreshOptions();
NPCName2->RefreshOptions();
}
else if (protocol.cmd == FNetCommand::AiChatGenerate)
{
if (protocol.status == -1)
{
//需要重新生成
GenerateButton->SetEnabled(true);
UE_LOG(LogTemp, Error, TEXT("Chat Generate Failed reson = %s"), *protocol.message);
}
else if (protocol.status == 0)
{
UE_LOG(LogTemp, Warning, TEXT("Chat Generate warning reson = %s"), *protocol.message);
}
else if (protocol.status == 1)
{
UE_LOG(LogTemp, Warning, TEXT("Chat Generate success chat = %s"), *protocol.data);
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
if (ModulePtr) {
FString DataPath = FString::Printf(TEXT("/Game/Test/%s.%s"), *DataName->GetInputText(), *DataName->GetInputText());
ModulePtr->CreateOrAddData(DataPath, protocol.data);
}
}
else if (protocol.status == 2)
{
//全部生成完成
UE_LOG(LogTemp, Warning, TEXT("Chat Generate has all generated"));
GenerateButton->SetEnabled(true);
}
}
FString SAIGCWindow::GeneratePromptJson()
{
FPrompt PromptData;
PromptData.DialogContent = FDialogContent(DialogScene->GetInputText());
PromptData.Persons.Add(FPersonInfo(NPCName1->GetInputText(), NPCJob1->GetInputText()));
PromptData.Persons.Add(FPersonInfo(NPCName2->GetInputText(), NPCJob2->GetInputText()));
// 结构体转JSON字符串
FString OutputString;
FJsonObjectConverter::UStructToJsonObjectString(
FPrompt::StaticStruct(),
&PromptData,
OutputString,
0,
0
);
return OutputString;
} }

View File

@ -12,7 +12,6 @@
void SCharacterWindow::Construct(const FArguments& InArgs) void SCharacterWindow::Construct(const FArguments& InArgs)
{ {
ChildSlot[ ChildSlot[
SNew(SVerticalBox) SNew(SVerticalBox)
+SVerticalBox::Slot() +SVerticalBox::Slot()
@ -129,12 +128,12 @@ EActiveTimerReturnType SCharacterWindow::OnPostPaint(double X, float Arg)
} }
else else
{ {
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT("")); WebSocketManager->SendData(FNetCommand::RequestCharacterInfos, TEXT(""));
} }
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SCharacterWindow::HandleReceiveData); WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SCharacterWindow::HandleReceiveData);
WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess) WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess)
{ {
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT("")); WebSocketManager->SendData(FNetCommand::RequestCharacterInfos, TEXT(""));
}); });
} }
return EActiveTimerReturnType::Stop; return EActiveTimerReturnType::Stop;
@ -142,7 +141,7 @@ EActiveTimerReturnType SCharacterWindow::OnPostPaint(double X, float Arg)
void SCharacterWindow::HandleReceiveData(FNetProtocol protocol) void SCharacterWindow::HandleReceiveData(FNetProtocol protocol)
{ {
if (protocol.cmd == FNetCommand::CharacterList) if (protocol.cmd == FNetCommand::RequestCharacterInfos)
{ {
//解析json角色信息 //解析json角色信息

View File

@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/ConfigItem_ComboBox.h"

View File

@ -50,6 +50,7 @@ public:
void CreateOrAddData(const FString& DataTablePath, const FString& RowValue); void CreateOrAddData(const FString& DataTablePath, const FString& RowValue);
class UWebSocketManager* GetWebSocketManager(); class UWebSocketManager* GetWebSocketManager();
void InitWebSocketManager(); void InitWebSocketManager();
//void OnWebSocketConnect(bool bSuccess);
private: private:
TSharedPtr<class FUICommandList> PluginCommands; TSharedPtr<class FUICommandList> PluginCommands;

View File

@ -3,49 +3,42 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Definations.generated.h" #include "Definations.generated.h"
// USTRUCT()
// struct FDialogContent
// {
// GENERATED_BODY()
// FDialogContent() {};
//
// FDialogContent(const FString& InDialogScene) : DialogScene(InDialogScene) {}
// UPROPERTY()
// FString DialogScene;
// };
// USTRUCT()
// struct FPersonInfo
// {
// GENERATED_BODY()
// FPersonInfo() {}
// FPersonInfo(const FString& InName, const FString& InJob):
// Name(InName), Job(InJob){}
// UPROPERTY()
// FString Name;
// UPROPERTY()
// FString Job;
//
// };
USTRUCT() USTRUCT()
struct FDialogContent struct FRequestAIChat
{ {
GENERATED_BODY() GENERATED_BODY()
FDialogContent() {};
FDialogContent(const FString& InDialogScene) : DialogScene(InDialogScene) {}
UPROPERTY() UPROPERTY()
FString DialogScene; FString DialogScene;
UPROPERTY()
int32 GenerateCount;
UPROPERTY()
TArray<FString> CharacterName;
}; };
USTRUCT()
struct FPersonInfo
{
GENERATED_BODY()
FPersonInfo() {}
FPersonInfo(const FString& InName, const FString& InJob):
Name(InName), Job(InJob){}
UPROPERTY()
FString Name;
UPROPERTY()
FString Job;
};
USTRUCT()
struct FPrompt
{
GENERATED_BODY()
UPROPERTY()
FDialogContent DialogContent;
UPROPERTY()
TArray<FPersonInfo> Persons;
};
USTRUCT()
struct FAIServerData
{
GENERATED_BODY()
UPROPERTY()
int32 statusCode;
UPROPERTY()
TArray<FString> messages;
};
USTRUCT() USTRUCT()
struct FCharacterInfo struct FCharacterInfo
@ -92,7 +85,8 @@ USTRUCT()
struct FNetCommand struct FNetCommand
{ {
GENERATED_BODY() GENERATED_BODY()
static FString CharacterList; static FString RequestCharacterInfos;
static FString RequestCharacterNames;
static FString AddCharacter; static FString AddCharacter;
static FString AiChatGenerate; static FString AiChatGenerate;
}; };

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "ConfigItem_ComboBox.h"
#include "ConfigItem_NumberSpin.h" #include "ConfigItem_NumberSpin.h"
#include "ConfigItem_Text.h" #include "ConfigItem_Text.h"
#include "Definations.h" #include "Definations.h"
@ -26,19 +27,22 @@ public:
void HandleReceiveData(FNetProtocol protocol); void HandleReceiveData(FNetProtocol protocol);
private: private:
FString GeneratePromptJson();
int32 RequireGenerateCount; int32 RequireGenerateCount;
int32 GeneratedCount = 1; int32 GeneratedCount = 1;
FCharacterArray Characters;
TArray<TSharedPtr<FCharacterInfo>> CharacterInfos;
FCharacterInfo SelectedCharacter1;
FCharacterInfo SelectedCharacter2;
protected: protected:
TSharedPtr<SConfigItem_Text> ServerIP; //服务器IP
TSharedPtr<SConfigItem_Text> DataName; //datatable 名称 TSharedPtr<SConfigItem_Text> DataName; //datatable 名称
TSharedPtr<SConfigItem_Text> NPCName1; //npc1的名称 TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName1; //npc1的名称
TSharedPtr<SConfigItem_Text> NPCJob1; //npc1的职业 TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName2; //npc2的名称
TSharedPtr<SConfigItem_Text> NPCName2; //npc2的名称
TSharedPtr<SConfigItem_Text> NPCJob2; //npc2的职业
TSharedPtr<SConfigItem_Text> DialogScene; //对话场景 TSharedPtr<SConfigItem_Text> DialogScene; //对话场景
TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目 TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目
TSharedPtr<SButton> GenerateButton; //生成按钮 TSharedPtr<SButton> GenerateButton; //生成按钮
}; };

View File

@ -0,0 +1,69 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
/**
*
*/
template <typename OptionType>
class AIGC_API SConfigItem_ComboBox: public SCompoundWidget
{
public:
typedef typename TSlateDelegates<OptionType>::FOnGenerateWidget FOnGenerateWidget;
typedef typename TSlateDelegates<OptionType>::FOnSelectionChanged FOnSelectionChanged;
SLATE_BEGIN_ARGS( SConfigItem_ComboBox )
:_Title()
,_Content()
{}
SLATE_ARGUMENT(FText, Title) // 标题参数
SLATE_ITEMS_SOURCE_ARGUMENT( OptionType, OptionsSource ) //数据源
SLATE_EVENT(FOnGenerateWidget, OnGenerateWidget )
SLATE_EVENT( FOnSelectionChanged, OnSelectionChanged )
SLATE_NAMED_SLOT(FArguments, Content)//显示
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
//OptionSource = InArgs.GetOptionsSource();
ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(10, 0)
[
SNew(STextBlock)
.Text(InArgs._Title)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(10, 0)
[
SAssignNew(ComboBox, SComboBox<OptionType>)
.OptionsSource(InArgs.GetOptionsSource())
.Content()
[
InArgs._Content.Widget
]
.OnGenerateWidget(InArgs._OnGenerateWidget)
.OnSelectionChanged(InArgs._OnSelectionChanged)
]
];
}
void RefreshOptions()
{
ComboBox->RefreshOptions();
}
private:
TSharedPtr<SComboBox<OptionType>> ComboBox;
//TArray<OptionType>* OptionSource; //数据源指针
// FOnGenerateWidget OnGenerateWidgetDelegate;
// FOnSelectionChanged OnSelectionChangedDelegate;
};

View File

@ -25,6 +25,7 @@ public:
void Construct(const FArguments& InArgs) void Construct(const FArguments& InArgs)
{ {
InputValue = InArgs._MinNumber;
ChildSlot ChildSlot
[ [
SNew(SHorizontalBox) SNew(SHorizontalBox)