Compare commits

...

10 Commits

24 changed files with 753 additions and 170 deletions

3
.gitignore vendored
View File

@ -103,3 +103,6 @@ DerivedDataCache/*
/TestForAIGC/Plugins/HttpHelper/Binaries
/TestForAIGC/Plugins/HttpHelper/Content
/AIGC/aigc.log
/TestForAIGC/TestForAIGC.uproject.DotSettings.user
/AIGC/__pycache__/AICore.cpython-312.pyc
/AIGC/__pycache__/DatabaseHandle.cpython-312.pyc

21
AIGC/AICore.py Normal file
View File

@ -0,0 +1,21 @@
import requests
from ollama import Client, ResponseError
import tiktoken
class AICore:
modelMaxTokens = 128000
# 初始化 DeepSeek 使用的 Tokenizer (cl100k_base)
encoder = tiktoken.get_encoding("cl100k_base")
def __init__(self, model):
#初始化ollama客户端
self.ollamaClient = Client(host='http://localhost:11434', headers={'x-some-header': 'some-value'})
response = self.ollamaClient.show(model)
modelMaxTokens = response.modelinfo['qwen2.context_length']
def getPromptToken(self, prompt)-> int:
tokens = self.encoder.encode(prompt)
return len(tokens)

119
AIGC/DatabaseHandle.py Normal file
View File

@ -0,0 +1,119 @@
from contextlib import contextmanager
import sqlite3
class DatabaseHandle:
def __init__(self, db_path = "data.db"):
self.db_path = db_path
self._init_db()
def _init_db(self):
"""创建表"""
with self._get_connection() as conn:
#创建角色表
conn.execute('''
CREATE TABLE IF NOT EXISTS characters
(id INTEGER PRIMARY KEY,
name TEXT not NULL UNIQUE,
age INTEGER,
personality TEXT,
profession TEXT,
characterBackground TEXT,
chat_style TEXT
)
''')
#创建聊天记录表
conn.execute('''
CREATE TABLE IF NOT EXISTS chat_records
(id INTEGER PRIMARY KEY,
character_ids TEXT not NULL,
chat TEXT,
time DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
@contextmanager
def _get_connection(self):
conn = sqlite3.connect(self.db_path)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def add_character(self, data:dict):
"""添加角色数据"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO characters (
name, age, personality, profession, characterBackground,
chat_style
) VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
age = excluded.age,
personality = excluded.personality,
profession = excluded.profession,
characterBackground = excluded.characterBackground,
chat_style = excluded.chat_style
''', (
data["name"], data["age"], data["personality"],
data["profession"], data["characterBackground"],
data["chat_style"]
))
conn.commit()
return cursor.lastrowid
def get_character_byname(self, name:str) ->list:
"""
根据角色名称查询数据name为空时返回全部数据
:param name: 角色名称精确匹配None 或空字符串时返回全部
:return: 角色数据字典列表
"""
with self._get_connection() as conn:
cursor = conn.cursor()
# 动态构建SQL语句
sql = "SELECT * FROM characters"
params = ()
if name: # 当name非空时添加条件
sql += " WHERE name = ?"
params = (name,)
cursor.execute(sql, params)
# 转换结果为字典列表
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
def add_chat(self, data:dict):
"""添加聊天数据"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO chat_records (
character_ids, chat
) VALUES (?, ?)
''', (
data["character_ids"], data["chat"]
))
conn.commit()
return cursor.lastrowid
def get_chats_by_character_id(self, character_id: int) -> list:
"""
根据角色ID查询聊天记录target_id为空时返回全部数据
:param target_id: 目标角色IDNone时返回全部记录
:return: 聊天记录字典列表
"""
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)
# 转换结果为字典列表
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]

BIN
AIGC/data.db Normal file

Binary file not shown.

View File

@ -11,8 +11,10 @@ from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconn
from fastapi.websockets import WebSocketState
from h11 import ConnectionClosed
import uvicorn
from AICore import AICore
from DatabaseHandle import DatabaseHandle
from Utils.AIGCLog import AIGCLog
from ollama import Client, ResponseError
app = FastAPI(title = "AI 通信服务")
logger = AIGCLog(name = "AIGC", log_file = "aigc.log")
@ -27,8 +29,8 @@ logger.log(logging.INFO, f"使用的模型是 {args.model}")
maxAIRegerateCount = 5
lastPrompt = ""
#初始化ollama客户端
ollamaClient = Client(host='http://localhost:11434')
aicore = AICore(args.model)
async def heartbeat(websocket: WebSocket):
pass
@ -40,14 +42,10 @@ async def heartbeat(websocket: WebSocket):
# break # 连接已关闭时退出循环
#statuscode -1 服务器运行错误 0 心跳标志 1 正常 2 输出异常
async def senddata(websocket: WebSocket, statusCode: int, messages: List[str]):
async def senddata(websocket: WebSocket, protocol: dict):
# 将AI响应发送回UE5
if websocket.client_state == WebSocketState.CONNECTED:
data = {
"statuscode": statusCode,
"messages": messages
}
json_string = json.dumps(data, ensure_ascii=False)
json_string = json.dumps(protocol, ensure_ascii=False)
await websocket.send_text(json_string)
# WebSocket路由处理
@ -63,15 +61,17 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
# 接收UE5发来的消息
data = await websocket.receive_text()
logger.log(logging.INFO, f"收到UE5消息 [{client_id}]: {data}")
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, [])
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, [])
@ -83,6 +83,41 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
#manager.disconnect(client_id)
logger.log(logging.ERROR, f"WebSocket异常 [{client_id}]: {str(e)}")
async def handle_characterlist(client: WebSocket):
### 获得数据库中的角色信息###
characters = database.get_character_byname("")
protocol = {}
protocol["cmd"] = "CharacterList"
protocol["status"] = 1
protocol["message"] = "success"
characterforUE = {}
characterforUE["characterInfos"] = characters
protocol["data"] = json.dumps(characterforUE)
await senddata(client, protocol)
async def handle_addcharacter(client: WebSocket, chracterJson: str):
### 添加角色到数据库 ###
character_info = json.loads(chracterJson)
id = database.add_character(character_info)
logger.log(logging.INFO, f"添加角色到数据库 id = {id}")
# id = database.add_character({"name":"张三","age":35,"personality":"成熟稳重/惜字如金","profession":"阿里巴巴算法工程师"
# ,"characterBackground":"浙大计算机系毕业专注AI优化项目","chat_style":"请在对话中表现出专业、冷静、惜字如金。用口语化的方式简短回答"})
async def process_protocol_json(json_str: str, client: WebSocket):
### 处理协议JSON ###
try:
protocol = json.loads(json_str)
cmd = protocol.get("cmd")
data = protocol.get("data")
if cmd == "CharacterList":
await handle_characterlist(client)
elif cmd == "AddCharacter":
await handle_addcharacter(client, data)
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}")
def process_prompt(promptFromUE: str) -> Tuple[bool, str]:
try:
data = json.loads(promptFromUE)
@ -150,6 +185,7 @@ async def generateAIChat(promptStr: str, websocket: WebSocket| None = None):
{"role": "system", "content": promptStr}
]
try:
# response = ollamaClient.chat(
# model = args.model,
# stream = False,
@ -234,43 +270,57 @@ if __name__ == "__main__":
server_thread.daemon = True # 设为守护线程(主程序退出时自动终止)
server_thread.start()
# Test
asyncio.run(
generateAIChat(promptStr = f"""
#你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
#对话的世界观背景是2025年的都市背景
1. 生成2轮完整对话每轮包含双方各一次发言共4句
2.角色设定
#Test database
database = DatabaseHandle()
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 = database.get_chats_by_character_id(3)
if id == 0:
logger.log(logging.ERROR, f"角色 张三已经添加到数据库")
# Test AI
aicore.getPromptToken("测试功能")
# asyncio.run(
# generateAIChat(promptStr = f"""
# #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
# #对话的世界观背景是2025年的都市背景
# 1. 生成【2轮完整对话】每轮包含双方各一次发言共4句
# 2.角色设定
"张三": {{
"姓名": "张三",
"年龄": 35,
"性格": "成熟稳重/惜字如金",
"职业": "阿里巴巴算法工程师",
"背景": "浙大计算机系毕业专注AI优化项目",
"对话场景": "你正在和用户聊天,用户是你的同事",
"语言风格": "请在对话中表现出专业、冷静、惜字如金。用口语化的方式简短回答"
}},
"李明": {{
"姓名": "李明",
"年龄": 30,
"职业": "产品经理",
"性格": "活泼健谈"
"背景": "公司资深产品经理",
"对话场景": "你正在和用户聊天,用户是你的同事",
"语言风格": "热情"
}}
# "张三": {{
# "姓名": "张三",
# "年龄": 35,
# "性格": "成熟稳重/惜字如金",
# "职业": "阿里巴巴算法工程师",
# "背景": "浙大计算机系毕业专注AI优化项目",
# "对话场景": "你正在和用户聊天,用户是你的同事",
# "语言风格": "请在对话中表现出专业、冷静、惜字如金。用口语化的方式简短回答"
# }},
# "李明": {{
# "姓名": "李明",
# "年龄": 30,
# "职业": "产品经理",
# "性格": "活泼健谈"
# "背景": "公司资深产品经理",
# "对话场景": "你正在和用户聊天,用户是你的同事",
# "语言风格": "热情"
# }}
3.输出格式
<format>
张三[第一轮发言]
李明[第一轮回应]
张三[第二轮发言]
李明[第二轮回应]
</format>
"""
)
)
# 3.输出格式:
# <format>
# 张三:[第一轮发言]
# 李明:[第一轮回应]
# 张三:[第二轮发言]
# 李明:[第二轮回应]
# </format>
# """
# )
# )
try:
# 主线程永久挂起(监听退出信号)

View File

@ -1,3 +1,4 @@
uvicorn[standard]
fastapi
ollama
ollama
tiktoken

View File

@ -0,0 +1,2 @@
[Paths]
Prefix=..

View File

@ -0,0 +1,5 @@
[/Script/AIGC.AIGCSetting]
ServerIP=127.0.0.1

View File

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

View File

@ -2,42 +2,34 @@
#include "AIGC.h"
#include "AIGCSetting.h"
#include "AIGCStyle.h"
#include "AssetToolsModule.h"
#include "FileHelpers.h"
#include "ISettingsModule.h"
#include "LevelEditor.h"
#include "ToolMenu.h"
#include "ToolMenus.h"
#include "WebSocketManager.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Misc/MessageDialog.h"
#include "UObject/SavePackage.h"
#include "Widget/AIGCWindow.h"
#include "Widget/CharacterWindow.h"
#define LOCTEXT_NAMESPACE "FAIGCModule"
#define AIGCWindowName FName("AIGC")
#define CharacterWindowName FName("Character")
void FAIGCModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
RegeisterSettings();
//添加style
FAIGCStyle::Initialize();
FAIGCStyle::ReloadTextures();
// //注册Command
// FAIGCCommands::Register();
// PluginCommands = MakeShareable(new FUICommandList);
// //绑定动作
// PluginCommands->MapAction(
// FAIGCCommands::Get().MenuAction,
// FExecuteAction::CreateRaw(this, &FAIGCModule::MenuClicked),
// FCanExecuteAction());
//
// PluginCommands->MapAction(
// FAIGCCommands::Get().SubMenuAction,
// FExecuteAction::CreateRaw(this, &FAIGCModule::SubMenuClicked),
// FCanExecuteAction());
//添加菜单
UToolMenu* menu = UToolMenus::Get()->FindMenu(FName("LevelEditor.MainMenu.AIGC"));
if (!menu)
@ -62,16 +54,17 @@ void FAIGCModule::StartupModule()
FGlobalTabmanager::Get()->TryInvokeTab(AIGCWindowName);
})));
// // 填充第二个子菜单项
// FToolMenuSection& SubSection2 = SubMenu->AddSection(
// "SubSection2");
// SubSection.AddMenuEntry(
// "Submenu2",
// LOCTEXT("Action1Label", "生成模型2"),
// LOCTEXT("Action1Tooltip", "触发模型生成2"),
// FSlateIcon(FAIGCStyle::GetStyleSetName(), "AIGCStyle.AIChat"),
// FToolUIActionChoice(
// FAIGCCommands::Get().SubMenuAction, *PluginCommands));
//添加人员配置菜单
FToolMenuSection& CharacterSection = SubMenu->AddSection("Character");
SubSection.AddMenuEntry(
"Character",
LOCTEXT("Character", "角色配置"),
LOCTEXT("CharacterToolTip", ""),
FSlateIcon(FAIGCStyle::GetStyleSetName(), "AIGCStyle.AIChat"),
FUIAction(FExecuteAction::CreateLambda([this]()
{
FGlobalTabmanager::Get()->TryInvokeTab(CharacterWindowName);
})));
}),
false, // 是否动态生成
FSlateIcon());
@ -81,42 +74,54 @@ void FAIGCModule::StartupModule()
}
//注册窗口
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(AIGCWindowName, FOnSpawnTab::CreateRaw(this, &FAIGCModule::OnSpawnPluginTab))
//注册Chat窗口
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(AIGCWindowName, FOnSpawnTab::CreateRaw(this, &FAIGCModule::OnSpawnAIChatTab))
.SetDisplayName(LOCTEXT("AIGCWindow", "AIGCWindow"))
.SetMenuType(ETabSpawnerMenuType::Hidden);
//注册角色窗口
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(CharacterWindowName, FOnSpawnTab::CreateRaw(this, &FAIGCModule::OnSpawnCharacterTab))
.SetDisplayName(LOCTEXT("CharacterWindow", "CharacterWindow"))
.SetMenuType(ETabSpawnerMenuType::Hidden);
}
// void FAIGCModule::SubMenuClicked()
// {
// UE_LOG(LogTemp, Warning, TEXT("Generate Model"));
// FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("4444555"));
// }
void FAIGCModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
UnregisterSettings();
UToolMenus::UnregisterOwner(this);
FAIGCStyle::Shutdown();
//FAIGCCommands::Unregister();
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(AIGCWindowName);
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(CharacterWindowName);
}
void FAIGCModule::RegeisterSettings()
{
#if WITH_EDITOR
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (!SettingsModule)
return;
const auto Section = SettingsModule->RegisterSettings("Project", "Plugins", "AIGC",
FText::FromString(TEXT("AIGC")),
FText::FromString(TEXT("AIGC Setting")),
GetMutableDefault<UAIGCSetting>());
#endif
}
void FAIGCModule::UnregisterSettings()
{
#if WITH_EDITOR
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (SettingsModule)
SettingsModule->UnregisterSettings("Project", "Plugins", "AIGC");
#endif
}
TSharedRef<SDockTab> FAIGCModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
TSharedRef<SDockTab> FAIGCModule::OnSpawnAIChatTab(const FSpawnTabArgs& SpawnTabArgs)
{
FText WidgetText = FText::Format(
LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
FText::FromString(TEXT("FTestModule::OnSpawnPluginTab")),
FText::FromString(TEXT("Test.cpp"))
);
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
@ -130,6 +135,21 @@ TSharedRef<SDockTab> FAIGCModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTab
];
}
TSharedRef<SDockTab> FAIGCModule::OnSpawnCharacterTab(const FSpawnTabArgs& SpawnTabArgs)
{
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SCharacterWindow)
]
];
}
void FAIGCModule::CreateOrAddData(const FString& DataTablePath, const FString& RowValue)
{
FString PackagePath, AssetName;
@ -158,6 +178,18 @@ void FAIGCModule::CreateOrAddData(const FString& DataTablePath, const FString& R
UPackage::SavePackage(DataTable->GetPackage(), DataTable, *FilePath, SaveArgs);
}
UWebSocketManager* FAIGCModule::GetWebSocketManager()
{
return WebSocketManager;
}
void FAIGCModule::InitWebSocketManager()
{
auto& Settings = *GetDefault<UAIGCSetting>();
WebSocketManager = NewObject<UWebSocketManager>();
WebSocketManager->InitWebSocket(Settings.ServerIP);
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAIGCModule, AIGC)

View File

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

View File

@ -19,10 +19,16 @@ void UWebSocketManager::InitWebSocket(const FString& ServerIP)
,*ServerIP, *(FGuid::NewGuid().ToString()) )));
}
void UWebSocketManager::SendData(const FString& Data)
void UWebSocketManager::SendData(const FString& cmd, const FString& Data)
{
//定义客户端协议
FNetProtocol protocol;
protocol.cmd = cmd;
protocol.data = Data;
FString JsonString;
FJsonObjectConverter::UStructToJsonObjectString(protocol, JsonString);
WebSocketHandler->SendText(Data);
WebSocketHandler->SendText(JsonString);
}
void UWebSocketManager::HandleConnected()
@ -52,30 +58,38 @@ void UWebSocketManager::HandleOnMessageSend(const FString& Message)
void UWebSocketManager::HandleOnReceiveText(const FString& Message)
{
FAIServerData ServerData;
FJsonObjectConverter::JsonObjectStringToUStruct<FAIServerData>(
// FAIServerData ServerData;
// FJsonObjectConverter::JsonObjectStringToUStruct<FAIServerData>(
// Message,
// &ServerData,
// 0, // 检查标志位
// 0 // 转换标志位
// );
// switch (ServerData.statusCode)
// {
// case -1:
// UE_LOG(LogTemp, Error, TEXT("ai server error reason = %s "), *ServerData.messages[0]);
// OnDataReceiveDelaget.Broadcast(TArray<FString>());
// break;
// case 0:
// //normal heart tick
// break;
// case 1:
// UE_LOG(LogTemp, Warning, TEXT("Receive Text %s from WebSocket Server "), *Message);
// OnDataReceiveDelaget.Broadcast(ServerData.messages);
// break;
// case 2:
// UE_LOG(LogTemp, Error, TEXT("ai content generate format error,server is regerating..... "));
// break;
// default:
// break;
// }
FNetProtocol serverProtocol;
FJsonObjectConverter::JsonObjectStringToUStruct<FNetProtocol>(
Message,
&ServerData,
&serverProtocol,
0, // 检查标志位
0 // 转换标志位
);
switch (ServerData.statusCode)
{
case -1:
UE_LOG(LogTemp, Error, TEXT("ai server error reason = %s "), *ServerData.messages[0]);
OnDataReceiveDelaget.Broadcast(TArray<FString>());
break;
case 0:
//normal heart tick
break;
case 1:
UE_LOG(LogTemp, Warning, TEXT("Receive Text %s from WebSocket Server "), *Message);
OnDataReceiveDelaget.Broadcast(ServerData.messages);
break;
case 2:
UE_LOG(LogTemp, Error, TEXT("ai content generate format error,server is regerating..... "));
break;
default:
break;
}
OnDataReceiveDelaget.Broadcast(serverProtocol);
}

View File

@ -106,13 +106,23 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
EActiveTimerReturnType SAIGCWindow::OnPostPaint(double X, float Arg)
{
WebSocketManager = NewObject<UWebSocketManager>();
WebSocketManager->InitWebSocket(*ServerIP->GetInputText());
WebSocketManager->OnConnectDelegate.AddLambda([this](bool bSuccess)
//init websocket
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
if (ModulePtr)
{
GenerateButton->SetEnabled(bSuccess);
});
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData);
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
if (!WebSocketManager)
{
ModulePtr->InitWebSocketManager();
WebSocketManager = ModulePtr->GetWebSocketManager();
}
WebSocketManager->OnConnectDelegate.AddLambda([this](bool bSuccess)
{
GenerateButton->SetEnabled(bSuccess);
});
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData);
}
return EActiveTimerReturnType::Stop;
}
@ -122,40 +132,50 @@ void SAIGCWindow::OnAIGenerateClicked()
GenerateButton->SetEnabled(false);
RequireGenerateCount = GenerateCount->GetNumber();
UE_LOG(LogTemp, Warning, TEXT("生成次数 %d prompt:%s"), RequireGenerateCount, *GeneratePromptJson());
WebSocketManager->SendData(GeneratePromptJson());
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
if (ModulePtr)
{
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, GeneratePromptJson());
}
}
void SAIGCWindow::HandleReceiveData(TArray<FString> AiMessages)
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++;
WebSocketManager->SendData(GeneratePromptJson());
}
else
{
GeneratedCount = 0;
GenerateButton->SetEnabled(true);
UE_LOG(LogTemp, Warning, TEXT("生成结束!"));
}
// 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("生成结束!"));
// }
}

View File

@ -0,0 +1,187 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/CharacterWindow.h"
#include "AIGC.h"
#include "JsonObjectConverter.h"
#include "WebSocketManager.h"
#include "Widgets/Input/STextComboBox.h"
#define LOCTEXT_NAMESPACE "FAIGCModule"
void SCharacterWindow::Construct(const FArguments& InArgs)
{
ChildSlot[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
//角色列表
SAssignNew(CharacterComboBox, SComboBox<TSharedPtr<FCharacterInfo>>)
.OptionsSource(&CharacterInfos)
.Content()
[
SNew(STextBlock)
.Text_Lambda([this]() -> FText {
return FText::FromString(SelectedCharacter.name);
})
]
.OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
{
SelectedCharacter = *CharacterInfo;
this->CharacterWidget_Name->SetInputText(SelectedCharacter.name);
this->CharacterWidget_Age->SetNumber(SelectedCharacter.age);
this->CharacterWidget_Personality->SetInputText(SelectedCharacter.personality);
this->CharacterWidget_Profession->SetInputText(SelectedCharacter.profession);
this->CharacterWidget_BG->SetInputText(SelectedCharacter.characterBackground);
this->CharacterWidget_Style->SetInputText(SelectedCharacter.chat_style);
})
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_Name, SConfigItem_Text)
.Title(LOCTEXT("CharacterName", "角色名称"))
.HintText(LOCTEXT("HintCharacterName", "请输入角色名称"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_Age, SConfigItem_NumberSpin<int32>)
.Title(LOCTEXT("CharacterAge", "角色年龄"))
.MinNumber(1)
.MaxNumber(99)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_Personality, SConfigItem_Text)
.Title(LOCTEXT("CharacterName", "角色性格"))
.HintText(LOCTEXT("HintCharacterName", "请输入角色性格"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_Profession, SConfigItem_Text)
.Title(LOCTEXT("CharacterName", "角色职业"))
.HintText(LOCTEXT("HintCharacterName", "请输入角色职业"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_BG, SConfigItem_Text)
.Title(LOCTEXT("CharacterName", "角色背景"))
.HintText(LOCTEXT("HintCharacterName", "请输入角色背景"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SAssignNew(CharacterWidget_Style, SConfigItem_Text)
.Title(LOCTEXT("CharacterName", "角色对话风格"))
.HintText(LOCTEXT("HintCharacterName", "请输入角色对话风格"))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 10, 0, 0)
[
SNew( SButton)
.Text(LOCTEXT("AddCharacter", "添加角色到服务器中"))
.OnClicked_Lambda([this]()
{
AddCharacterToServer();
return FReply::Handled();
})
]
];
// 注册绘制后回调
RegisterActiveTimer(0.f,
FWidgetActiveTimerDelegate::CreateSP(
this,
&SCharacterWindow::OnPostPaint
)
);
}
EActiveTimerReturnType SCharacterWindow::OnPostPaint(double X, float Arg)
{
//init websocket
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
if (ModulePtr)
{
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
if (!WebSocketManager)
{
ModulePtr->InitWebSocketManager();
WebSocketManager = ModulePtr->GetWebSocketManager();
}
else
{
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT(""));
}
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SCharacterWindow::HandleReceiveData);
WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess)
{
WebSocketManager->SendData(FNetCommand::CharacterList, TEXT(""));
});
}
return EActiveTimerReturnType::Stop;
}
void SCharacterWindow::HandleReceiveData(FNetProtocol protocol)
{
if (protocol.cmd == FNetCommand::CharacterList)
{
//解析json角色信息
FJsonObjectConverter::JsonObjectStringToUStruct<FCharacterArray>(
protocol.data,
&Characters,
0, // 检查标志位
0 // 转换标志位
);
CharacterInfos.Empty();
for (auto Character: Characters.characterInfos)
{
CharacterInfos.Add(MakeShareable(new FCharacterInfo(Character)));
}
CharacterComboBox->RefreshOptions();
}
}
void SCharacterWindow::AddCharacterToServer()
{
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
if (ModulePtr)
{
//生成角色信息
FCharacterInfo CharacterInfo;
CharacterInfo.name = CharacterWidget_Name->GetInputText();
CharacterInfo.age = CharacterWidget_Age->GetNumber();
CharacterInfo.personality = CharacterWidget_Personality->GetInputText();
CharacterInfo.profession = CharacterWidget_Profession->GetInputText();
CharacterInfo.characterBackground = CharacterWidget_BG->GetInputText();
CharacterInfo.chat_style = CharacterWidget_Style->GetInputText();
FString CharacterJson;
FJsonObjectConverter::UStructToJsonObjectString(CharacterInfo, CharacterJson);
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
WebSocketManager->SendData(FNetCommand::AddCharacter, CharacterJson);
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -23,6 +23,7 @@ void SConfigItem_Text::Construct(const FArguments& InArgs)
[
SNew(SEditableTextBox)
.HintText( InArgs._HintText)
.VirtualKeyboardType(InArgs._KeyboardType)
.Text_Lambda([this, InArgs]() {
return FText::FromString(InputText);
})

View File

@ -37,13 +37,22 @@ struct FNPCChatRow : public FTableRowBase {
class FAIGCModule : public IModuleInterface
{
public:
TSharedRef<SDockTab> OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs);
TSharedRef<SDockTab> OnSpawnAIChatTab(const FSpawnTabArgs& SpawnTabArgs);
TSharedRef<SDockTab> OnSpawnCharacterTab(const FSpawnTabArgs& SpawnTabArgs);
//void SubMenuClicked();
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void RegeisterSettings();
void UnregisterSettings();
void CreateOrAddData(const FString& DataTablePath, const FString& RowValue);
class UWebSocketManager* GetWebSocketManager();
void InitWebSocketManager();
private:
TSharedPtr<class FUICommandList> PluginCommands;
class UWebSocketManager* WebSocketManager = nullptr;
};
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,19 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "AIGCSetting.generated.h"
/**
*
*/
UCLASS(Config=AIGCSetting, DefaultConfig)
class AIGC_API UAIGCSetting : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(Config, EditAnywhere)
FString ServerIP = TEXT("127.0.0.1");
};

View File

@ -45,4 +45,54 @@ struct FAIServerData
int32 statusCode;
UPROPERTY()
TArray<FString> messages;
};
};
USTRUCT()
struct FCharacterInfo
{
GENERATED_BODY()
UPROPERTY()
int32 id;
UPROPERTY()
FString name;
UPROPERTY()
int32 age;
UPROPERTY()
FString personality;
UPROPERTY()
FString profession;
UPROPERTY()
FString characterBackground;
UPROPERTY()
FString chat_style;
};
USTRUCT()
struct FCharacterArray
{
GENERATED_BODY()
UPROPERTY()
TArray<FCharacterInfo> characterInfos;
};
USTRUCT()
struct FNetProtocol
{
GENERATED_BODY()
UPROPERTY()
FString cmd; //命令名称
UPROPERTY()
int32 status; //状态
UPROPERTY()
FString message; //响应消息
UPROPERTY()
FString data; //json数据
};
USTRUCT()
struct FNetCommand
{
GENERATED_BODY()
static FString CharacterList;
static FString AddCharacter;
static FString AiChatGenerate;
};

View File

@ -3,11 +3,12 @@
#pragma once
#include "CoreMinimal.h"
#include "Definations.h"
#include "UObject/Object.h"
#include "WebSocketManager.generated.h"
DECLARE_MULTICAST_DELEGATE_OneParam(FOnConnectDelegate, bool);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDataReceiveDelaget, TArray<FString>);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDataReceiveDelaget, FNetProtocol);
/**
*
*/
@ -17,7 +18,7 @@ class AIGC_API UWebSocketManager : public UObject
GENERATED_BODY()
public:
void InitWebSocket(const FString& ServerIP);
void SendData(const FString& Data);
void SendData(const FString& cmd, const FString& Data);
UFUNCTION()
void HandleConnected();
UFUNCTION()

View File

@ -5,6 +5,7 @@
#include "CoreMinimal.h"
#include "ConfigItem_NumberSpin.h"
#include "ConfigItem_Text.h"
#include "Definations.h"
/**
*
@ -22,7 +23,7 @@ public:
void OnAIGenerateClicked();
void InitWebClient();
void HandleReceiveData(TArray<FString> String);
void HandleReceiveData(FNetProtocol protocol);
private:
FString GeneratePromptJson();
@ -39,6 +40,5 @@ protected:
TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目
TSharedPtr<SButton> GenerateButton; //生成按钮
UPROPERTY()
class UWebSocketManager* WebSocketManager;
};

View File

@ -0,0 +1,40 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ConfigItem_NumberSpin.h"
#include "ConfigItem_Text.h"
#include "Definations.h"
/**
*
*/
class AIGC_API SCharacterWindow: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SCharacterWindow)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
EActiveTimerReturnType OnPostPaint(double X, float Arg);
void HandleReceiveData(FNetProtocol protocol);
void AddCharacterToServer();
protected:
TSharedPtr<SComboBox<TSharedPtr<FCharacterInfo>>> CharacterComboBox;
TSharedPtr<SConfigItem_Text> CharacterWidget_Name; //角色名称
TSharedPtr<SConfigItem_NumberSpin<int32>> CharacterWidget_Age; //角色年龄
TSharedPtr<SConfigItem_Text> CharacterWidget_Personality; //角色性格
TSharedPtr<SConfigItem_Text> CharacterWidget_Profession; //角色专业
TSharedPtr<SConfigItem_Text> CharacterWidget_BG; //角色背景
TSharedPtr<SConfigItem_Text> CharacterWidget_Style; //说话风格
FCharacterArray Characters;
TArray<TSharedPtr<FCharacterInfo>> CharacterInfos;
FCharacterInfo SelectedCharacter;
};

View File

@ -53,11 +53,10 @@ public:
];
}
NumericType GetNumber() const { return InputValue; }
void SetNumber(NumericType InValue) { InputValue = InValue; }
private:
void OnValueChanged(NumericType NewValue)
{
UE_LOG(LogTemp, Display, TEXT("OnValueChanged newvalue = %d"), (int32)NewValue);
InputValue = NewValue;
Invalidate(EInvalidateWidget::Layout);
}

View File

@ -13,17 +13,18 @@ public:
: _Title()
, _HintText()
, _DefaultText()
,_KeyboardType(EKeyboardType::Keyboard_Default)
{}
SLATE_ARGUMENT(FText, Title) // 标题参数
SLATE_ARGUMENT(FText, HintText) // 默认值参数
SLATE_ARGUMENT(FText, DefaultText) // 默认值参数
SLATE_ARGUMENT(EKeyboardType, KeyboardType)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
FString GetInputText() const { return InputText; }
void SetInputText(const FString& NewText) { InputText = NewText; }
private:
FString InputText;