Compare commits
4 Commits
5ce62f0bb5
...
2984aaa4be
Author | SHA1 | Date | |
---|---|---|---|
2984aaa4be | |||
873dd45584 | |||
d1326b7776 | |||
23b62b60a5 |
@ -1,21 +1,47 @@
|
||||
import logging
|
||||
from typing import Tuple
|
||||
import requests
|
||||
from ollama import Client, ResponseError
|
||||
import tiktoken
|
||||
import random
|
||||
from Utils.AIGCLog import AIGCLog
|
||||
|
||||
class AICore:
|
||||
modelMaxTokens = 128000
|
||||
# 初始化 DeepSeek 使用的 Tokenizer (cl100k_base)
|
||||
encoder = tiktoken.get_encoding("cl100k_base")
|
||||
logger = AIGCLog(name = "AIGC", log_file = "aigc.log")
|
||||
|
||||
|
||||
|
||||
def __init__(self, model):
|
||||
#初始化ollama客户端
|
||||
self.ollamaClient = Client(host='http://localhost:11434', headers={'x-some-header': 'some-value'})
|
||||
self.modelName = model
|
||||
response = self.ollamaClient.show(model)
|
||||
modelMaxTokens = response.modelinfo['qwen2.context_length']
|
||||
|
||||
def getPromptToken(self, prompt)-> int:
|
||||
tokens = self.encoder.encode(prompt)
|
||||
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, "未预料错误"
|
@ -103,17 +103,18 @@ class DatabaseHandle:
|
||||
conn.commit()
|
||||
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为空时返回全部数据)
|
||||
:param target_id: 目标角色ID(None时返回全部记录)
|
||||
:return: 聊天记录字典列表
|
||||
"""
|
||||
|
||||
sorted_ids = sorted(character_id.split(","), key=int) # 按数值升序
|
||||
normalized_param = ",".join(sorted_ids)
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
sql = "SELECT * FROM chat_records WHERE ',' || character_ids || ',' LIKE '%,' || ? || ',%'"
|
||||
params = (str(character_id))
|
||||
cursor.execute(sql, params)
|
||||
# 转换结果为字典列表
|
||||
sql = "SELECT * FROM chat_records WHERE character_ids = ?"
|
||||
cursor.execute(sql, (normalized_param,))
|
||||
columns = [col[0] for col in cursor.description]
|
||||
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
@ -2,7 +2,7 @@
|
||||
chcp 65001 > nul
|
||||
set OLLAMA_MODEL=deepseek-r1:7b
|
||||
rem 启动Ollama服务
|
||||
start "Ollama DeepSeek" cmd /k ollama run %OLLAMA_MODEL%
|
||||
start "Ollama DeepSeek" cmd /k ollama serve
|
||||
|
||||
rem 检测11434端口是否就绪
|
||||
echo 等待Ollama服务启动...
|
||||
|
BIN
AIGC/data.db
BIN
AIGC/data.db
Binary file not shown.
295
AIGC/main.py
295
AIGC/main.py
@ -26,11 +26,15 @@ parser.add_argument('--model', type=str, default='deepseek-r1:1.5b',
|
||||
args = parser.parse_args()
|
||||
logger.log(logging.INFO, f"使用的模型是 {args.model}")
|
||||
|
||||
maxAIRegerateCount = 5
|
||||
maxAIRegerateCount = 5 #最大重新生成次数
|
||||
regenerateCount = 1 #当前重新生成次数
|
||||
totalAIGenerateCount = 1 #客户端生成AI响应总数
|
||||
currentGenerateCount = 0 #当前生成次数
|
||||
lastPrompt = ""
|
||||
|
||||
character_id1 = 0
|
||||
character_id2 = 0
|
||||
aicore = AICore(args.model)
|
||||
|
||||
database = DatabaseHandle()
|
||||
|
||||
async def heartbeat(websocket: WebSocket):
|
||||
pass
|
||||
@ -48,6 +52,18 @@ async def senddata(websocket: WebSocket, protocol: dict):
|
||||
json_string = json.dumps(protocol, ensure_ascii=False)
|
||||
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路由处理
|
||||
@app.websocket("/ws/{client_id}")
|
||||
async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
||||
@ -62,20 +78,7 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
||||
data = await websocket.receive_text()
|
||||
logger.log(logging.INFO, f"收到UE5消息 [{client_id}]: {data}")
|
||||
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:
|
||||
#manager.disconnect(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("")
|
||||
protocol = {}
|
||||
protocol["cmd"] = "CharacterList"
|
||||
protocol["cmd"] = "RequestCharacterInfos"
|
||||
protocol["status"] = 1
|
||||
protocol["message"] = "success"
|
||||
characterforUE = {}
|
||||
@ -95,6 +98,84 @@ async def handle_characterlist(client: WebSocket):
|
||||
protocol["data"] = json.dumps(characterforUE)
|
||||
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):
|
||||
### 添加角色到数据库 ###
|
||||
character_info = json.loads(chracterJson)
|
||||
@ -109,10 +190,14 @@ async def process_protocol_json(json_str: str, client: WebSocket):
|
||||
protocol = json.loads(json_str)
|
||||
cmd = protocol.get("cmd")
|
||||
data = protocol.get("data")
|
||||
if cmd == "CharacterList":
|
||||
if cmd == "RequestCharacterInfos":
|
||||
await handle_characterlist(client)
|
||||
elif cmd == "RequestCharacterNames":
|
||||
await handle_characternames(client)
|
||||
elif cmd == "AddCharacter":
|
||||
await handle_addcharacter(client, data)
|
||||
elif cmd == "AiChatGenerate":
|
||||
await handle_aichat_generate(client, data)
|
||||
|
||||
except json.JSONDecodeError as 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]:
|
||||
try:
|
||||
data = json.loads(promptFromUE)
|
||||
global maxAIRegerateCount
|
||||
# 提取数据
|
||||
dialog_scene = data["dialogContent"]["dialogScene"]
|
||||
persons = data["persons"]
|
||||
dialog_scene = data["dialogScene"]
|
||||
global totalAIGenerateCount
|
||||
totalAIGenerateCount = data["generateCount"]
|
||||
persons = data["characterName"]
|
||||
|
||||
assert len(persons) == 2
|
||||
for person in persons:
|
||||
print(f" 姓名: {person['name']}, 职业: {person['job']}")
|
||||
|
||||
characterInfo1 = database.get_character_byname(persons[0])
|
||||
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"""
|
||||
你是一个游戏NPC对话生成器。请严格按以下要求生成两个路人NPC({persons[0]["name"]}和{persons[1]["name"]})的日常对话:
|
||||
1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
||||
2. 对话场景:{dialog_scene}
|
||||
3. 角色设定:
|
||||
{persons[0]["name"]}:{persons[0]["job"]}
|
||||
{persons[1]["name"]}:{persons[1]["job"]}
|
||||
4. 对话要求:
|
||||
* 每轮对话需自然衔接,体现生活细节
|
||||
* 避免任务指引或玩家交互内容
|
||||
* 结尾保持对话未完成感
|
||||
5. 输出格式:
|
||||
<format>
|
||||
{persons[0]["name"]}:[第一轮发言]
|
||||
{persons[1]["name"]}:[第一轮回应]
|
||||
{persons[0]["name"]}:[第二轮发言]
|
||||
{persons[1]["name"]}:[第二轮回应]
|
||||
</format>
|
||||
|
||||
6.重要!若未按此格式输出,请重新生成直至完全符合
|
||||
#你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
|
||||
#对话的背景
|
||||
{dialog_scene}
|
||||
1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
||||
2.角色设定
|
||||
{characterInfo1[0]["name"]}: {{
|
||||
"姓名": {characterInfo1[0]["name"]},
|
||||
"年龄": {characterInfo1[0]["age"]},
|
||||
"性格": {characterInfo1[0]["personality"]},
|
||||
"职业": {characterInfo1[0]["profession"]},
|
||||
"背景": {characterInfo1[0]["characterBackground"]},
|
||||
"语言风格": {characterInfo1[0]["chat_style"]}
|
||||
}},
|
||||
{characterInfo2[0]["name"]}: {{
|
||||
"姓名": {characterInfo2[0]["name"]},
|
||||
"年龄": {characterInfo2[0]["age"]},
|
||||
"性格": {characterInfo2[0]["personality"]},
|
||||
"职业": {characterInfo2[0]["profession"]},
|
||||
"背景": {characterInfo2[0]["characterBackground"]},
|
||||
"语言风格": {characterInfo2[0]["chat_style"]}
|
||||
}}
|
||||
3.参考的历史对话内容
|
||||
{result}
|
||||
4.输出格式:
|
||||
<format>
|
||||
张三:[第一轮发言]
|
||||
李明:[第一轮回应]
|
||||
张三:[第二轮发言]
|
||||
李明:[第二轮回应]
|
||||
</format>
|
||||
5.重要!若未按此格式输出,请重新生成直至完全符合
|
||||
"""
|
||||
return True, prompt
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON解析错误: {e}")
|
||||
return False, ""
|
||||
except KeyError as e:
|
||||
print(f"缺少必要字段: {e}")
|
||||
except Exception as e:
|
||||
print(f"发生错误:{type(e).__name__} - {e}")
|
||||
return False, ""
|
||||
|
||||
|
||||
|
||||
@ -173,91 +281,15 @@ def run_webserver():
|
||||
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):
|
||||
global regenerateCount
|
||||
if regenerateCount < maxAIRegerateCount:
|
||||
regenerateCount += 1
|
||||
logger.log(logging.ERROR, f"AI输出格式不正确,重新进行生成 {regenerateCount}/{maxAIRegerateCount}")
|
||||
await senddata(websocket, 2, messages=["ai生成格式不正确, 重新进行生成"])
|
||||
await sendprotocol(websocket, "AiChatGenerate", 0, "ai生成格式不正确, 重新进行生成", "")
|
||||
await asyncio.sleep(0)
|
||||
prompt = prompt + "补充:上一次的输出格式错误,严格执行prompt中第5条的输出格式要求"
|
||||
await generateAIChat(prompt, websocket)
|
||||
prompt = prompt + "补充:上一次的输出格式错误,严格执行prompt中的输出格式要求"
|
||||
await generate_aichat(prompt, websocket)
|
||||
return True
|
||||
else:
|
||||
regenerateCount = 0
|
||||
@ -272,19 +304,18 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
#Test database
|
||||
database = DatabaseHandle()
|
||||
|
||||
|
||||
|
||||
id = database.add_character({"name":"李明","age":30,"personality":"活泼健谈","profession":"产品经理"
|
||||
,"characterBackground":"公司资深产品经理","chat_style":"热情"})
|
||||
# id = database.add_character({"name":"李明","age":30,"personality":"活泼健谈","profession":"产品经理"
|
||||
# ,"characterBackground":"公司资深产品经理","chat_style":"热情"})
|
||||
|
||||
characters = database.get_character_byname("")
|
||||
#characters = database.get_character_byname("")
|
||||
#chat_id = database.add_chat({"character_ids":"1,2","chat":"张三:[第一轮发言] 李明:[第一轮回应] 张三:[第二轮发言] 李明:[第二轮回应"})
|
||||
chat = database.get_chats_by_character_id(3)
|
||||
if id == 0:
|
||||
logger.log(logging.ERROR, f"角色 张三已经添加到数据库")
|
||||
#chat = database.get_chats_by_character_id(3)
|
||||
#
|
||||
# Test AI
|
||||
aicore.getPromptToken("测试功能")
|
||||
#aicore.getPromptToken("测试功能")
|
||||
# asyncio.run(
|
||||
# generateAIChat(promptStr = f"""
|
||||
# #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
#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::AiChatGenerate = TEXT("AiChatGenerate");
|
@ -188,8 +188,19 @@ void FAIGCModule::InitWebSocketManager()
|
||||
auto& Settings = *GetDefault<UAIGCSetting>();
|
||||
WebSocketManager = NewObject<UWebSocketManager>();
|
||||
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
|
||||
|
||||
IMPLEMENT_MODULE(FAIGCModule, AIGC)
|
@ -15,15 +15,6 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
|
||||
{
|
||||
ChildSlot[
|
||||
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()
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
@ -36,35 +27,48 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
[
|
||||
SAssignNew(NPCName1, SConfigItem_Text)
|
||||
SAssignNew(NPCName1, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
|
||||
.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()
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
[
|
||||
SAssignNew(NPCJob1, SConfigItem_Text)
|
||||
.Title(LOCTEXT("Job1", "职业1"))
|
||||
.HintText(LOCTEXT("HintJon", "请输入职业"))
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
[
|
||||
SAssignNew(NPCName2, SConfigItem_Text)
|
||||
.Title(LOCTEXT("Name2", "名称2"))
|
||||
.HintText(LOCTEXT("HintName", "请输入名称"))
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
[
|
||||
SAssignNew(NPCJob2, SConfigItem_Text)
|
||||
.Title(LOCTEXT("Job2", "职业2"))
|
||||
.HintText(LOCTEXT("HintJon", "请输入职业"))
|
||||
]
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
[
|
||||
SAssignNew(NPCName2, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
|
||||
.Title(LOCTEXT("Name2", "名称2"))
|
||||
.OptionsSource(&CharacterInfos)
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() -> FText {
|
||||
|
||||
return FText::FromString(SelectedCharacter2.name);
|
||||
})
|
||||
]
|
||||
.OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
|
||||
return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
|
||||
})
|
||||
.OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
|
||||
{
|
||||
SelectedCharacter2 = *CharacterInfo;
|
||||
})
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(10)
|
||||
@ -111,14 +115,19 @@ EActiveTimerReturnType SAIGCWindow::OnPostPaint(double X, float Arg)
|
||||
if (ModulePtr)
|
||||
{
|
||||
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
|
||||
if (!WebSocketManager)
|
||||
if (!IsValid(WebSocketManager))
|
||||
{
|
||||
ModulePtr->InitWebSocketManager();
|
||||
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);
|
||||
WebSocketManager->SendData(FNetCommand::RequestCharacterNames, TEXT(""));
|
||||
});
|
||||
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData);
|
||||
|
||||
@ -131,11 +140,27 @@ void SAIGCWindow::OnAIGenerateClicked()
|
||||
{
|
||||
GenerateButton->SetEnabled(false);
|
||||
RequireGenerateCount = GenerateCount->GetNumber();
|
||||
UE_LOG(LogTemp, Warning, TEXT("生成次数 %d prompt:%s"), RequireGenerateCount, *GeneratePromptJson());
|
||||
|
||||
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)
|
||||
{
|
||||
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, GeneratePromptJson());
|
||||
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, dataJson);
|
||||
}
|
||||
|
||||
}
|
||||
@ -143,60 +168,55 @@ void SAIGCWindow::OnAIGenerateClicked()
|
||||
|
||||
void SAIGCWindow::HandleReceiveData(FNetProtocol protocol)
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("AI当前进度 %d/%d"), GeneratedCount, RequireGenerateCount);
|
||||
// //添加数据到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("生成结束!"));
|
||||
// }
|
||||
if (protocol.cmd == FNetCommand::RequestCharacterNames)
|
||||
{
|
||||
|
||||
//解析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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
@ -12,7 +12,6 @@
|
||||
void SCharacterWindow::Construct(const FArguments& InArgs)
|
||||
{
|
||||
|
||||
|
||||
ChildSlot[
|
||||
SNew(SVerticalBox)
|
||||
+SVerticalBox::Slot()
|
||||
@ -129,12 +128,12 @@ EActiveTimerReturnType SCharacterWindow::OnPostPaint(double X, float Arg)
|
||||
}
|
||||
else
|
||||
{
|
||||
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT(""));
|
||||
WebSocketManager->SendData(FNetCommand::RequestCharacterInfos, TEXT(""));
|
||||
}
|
||||
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SCharacterWindow::HandleReceiveData);
|
||||
WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess)
|
||||
{
|
||||
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT(""));
|
||||
WebSocketManager->SendData(FNetCommand::RequestCharacterInfos, TEXT(""));
|
||||
});
|
||||
}
|
||||
return EActiveTimerReturnType::Stop;
|
||||
@ -142,7 +141,7 @@ EActiveTimerReturnType SCharacterWindow::OnPostPaint(double X, float Arg)
|
||||
|
||||
void SCharacterWindow::HandleReceiveData(FNetProtocol protocol)
|
||||
{
|
||||
if (protocol.cmd == FNetCommand::CharacterList)
|
||||
if (protocol.cmd == FNetCommand::RequestCharacterInfos)
|
||||
{
|
||||
|
||||
//解析json角色信息
|
||||
|
@ -0,0 +1,5 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "Widget/ConfigItem_ComboBox.h"
|
||||
|
@ -50,6 +50,7 @@ public:
|
||||
void CreateOrAddData(const FString& DataTablePath, const FString& RowValue);
|
||||
class UWebSocketManager* GetWebSocketManager();
|
||||
void InitWebSocketManager();
|
||||
//void OnWebSocketConnect(bool bSuccess);
|
||||
|
||||
private:
|
||||
TSharedPtr<class FUICommandList> PluginCommands;
|
||||
|
@ -3,49 +3,42 @@
|
||||
#include "CoreMinimal.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()
|
||||
struct FDialogContent
|
||||
struct FRequestAIChat
|
||||
{
|
||||
GENERATED_BODY()
|
||||
FDialogContent() {};
|
||||
|
||||
FDialogContent(const FString& InDialogScene) : DialogScene(InDialogScene) {}
|
||||
UPROPERTY()
|
||||
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()
|
||||
struct FCharacterInfo
|
||||
@ -92,7 +85,8 @@ USTRUCT()
|
||||
struct FNetCommand
|
||||
{
|
||||
GENERATED_BODY()
|
||||
static FString CharacterList;
|
||||
static FString RequestCharacterInfos;
|
||||
static FString RequestCharacterNames;
|
||||
static FString AddCharacter;
|
||||
static FString AiChatGenerate;
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ConfigItem_ComboBox.h"
|
||||
#include "ConfigItem_NumberSpin.h"
|
||||
#include "ConfigItem_Text.h"
|
||||
#include "Definations.h"
|
||||
@ -26,19 +27,22 @@ public:
|
||||
void HandleReceiveData(FNetProtocol protocol);
|
||||
|
||||
private:
|
||||
FString GeneratePromptJson();
|
||||
|
||||
int32 RequireGenerateCount;
|
||||
int32 GeneratedCount = 1;
|
||||
FCharacterArray Characters;
|
||||
TArray<TSharedPtr<FCharacterInfo>> CharacterInfos;
|
||||
FCharacterInfo SelectedCharacter1;
|
||||
FCharacterInfo SelectedCharacter2;
|
||||
|
||||
protected:
|
||||
TSharedPtr<SConfigItem_Text> ServerIP; //服务器IP
|
||||
TSharedPtr<SConfigItem_Text> DataName; //datatable 名称
|
||||
TSharedPtr<SConfigItem_Text> NPCName1; //npc1的名称
|
||||
TSharedPtr<SConfigItem_Text> NPCJob1; //npc1的职业
|
||||
TSharedPtr<SConfigItem_Text> NPCName2; //npc2的名称
|
||||
TSharedPtr<SConfigItem_Text> NPCJob2; //npc2的职业
|
||||
TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName1; //npc1的名称
|
||||
TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName2; //npc2的名称
|
||||
TSharedPtr<SConfigItem_Text> DialogScene; //对话场景
|
||||
TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目
|
||||
TSharedPtr<SButton> GenerateButton; //生成按钮
|
||||
|
||||
|
||||
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@ public:
|
||||
|
||||
void Construct(const FArguments& InArgs)
|
||||
{
|
||||
InputValue = InArgs._MinNumber;
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
|
Loading…
x
Reference in New Issue
Block a user