Compare commits

...

16 Commits

28 changed files with 1113 additions and 338 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

47
AIGC/AICore.py Normal file
View File

@ -0,0 +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, "未预料错误"

120
AIGC/DatabaseHandle.py Normal file
View File

@ -0,0 +1,120 @@
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: str) -> list:
"""
根据角色ID查询聊天记录target_id为空时返回全部数据
:param target_id: 目标角色IDNone时返回全部记录
: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 = ?"
cursor.execute(sql, (normalized_param,))
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]

View File

@ -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 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")
@ -24,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 = ""
#初始化ollama客户端
ollamaClient = Client(host='http://localhost:11434')
character_id1 = 0
character_id2 = 0
aicore = AICore(args.model)
database = DatabaseHandle()
async def heartbeat(websocket: WebSocket):
pass
@ -40,14 +46,22 @@ 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)
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路由处理
@ -63,19 +77,8 @@ 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)
except WebSocketDisconnect:
#manager.disconnect(client_id)
logger.log(logging.WARNING, f"UE5客户端主动断开 [{client_id}]")
@ -83,45 +86,185 @@ 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"] = "RequestCharacterInfos"
protocol["status"] = 1
protocol["message"] = "success"
characterforUE = {}
characterforUE["characterInfos"] = characters
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)
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 == "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}")
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, ""
@ -138,76 +281,15 @@ def run_webserver():
log_level="info"
)
async def generateAIChat(prompt: str, websocket: WebSocket):
#动态标识吗 防止重复输入导致的结果重复
dynamic_token = str(int(time.time() % 1000))
prompt = f"""
[动态标识码:{dynamic_token}]
""" + prompt
logger.log(logging.INFO, "prompt:" + prompt)
starttime = time.time()
receivemessage=[
{"role": "system", "content": prompt}
]
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
}
)
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))
logger.log(logging.INFO, "AI生成" + response['message']['content'])
#处理ai输出内容
think_remove_text = re.sub(r'<think>.*?</think>', '', response['message']['content'], 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
@ -220,29 +302,57 @@ if __name__ == "__main__":
server_thread.daemon = True # 设为守护线程(主程序退出时自动终止)
server_thread.start()
## Test
# generateAIChat(f"""
# 你是一个游戏NPC对话生成器。请严格按以下要求生成两个路人NPCA和B的日常对话
# 1. 生成【2轮完整对话】每轮包含双方各一次发言共4句
# 2. 对话场景:中世纪奇幻小镇的日常场景(如市场/酒馆/街道)
# 3. 角色设定:
# - NPC A随机职业铁匠/农夫/商人/卫兵等)
# - NPC B随机职业不同于A
# 4. 对话要求:
# * 每轮对话需自然衔接,体现生活细节
# * 避免任务指引或玩家交互内容
# * 结尾保持对话未完成感
# 5. 输出格式(严格遵循,
# ---
# A[第一轮发言]
# B[第一轮回应]
# A[第二轮发言]
# B[第二轮回应]
# ---
# """
#Test database
# 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)
#
# Test AI
#aicore.getPromptToken("测试功能")
# asyncio.run(
# generateAIChat(promptStr = f"""
# #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
# #对话的世界观背景是2025年的都市背景
# 1. 生成【2轮完整对话】每轮包含双方各一次发言共4句
# 2.角色设定
# "张三": {{
# "姓名": "张三",
# "年龄": 35,
# "性格": "成熟稳重/惜字如金",
# "职业": "阿里巴巴算法工程师",
# "背景": "浙大计算机系毕业专注AI优化项目",
# "对话场景": "你正在和用户聊天,用户是你的同事",
# "语言风格": "请在对话中表现出专业、冷静、惜字如金。用口语化的方式简短回答"
# }},
# "李明": {{
# "姓名": "李明",
# "年龄": 30,
# "职业": "产品经理",
# "性格": "活泼健谈"
# "背景": "公司资深产品经理",
# "对话场景": "你正在和用户聊天,用户是你的同事",
# "语言风格": "热情"
# }}
# 3.输出格式:
# <format>
# 张三:[第一轮发言]
# 李明:[第一轮回应]
# 张三:[第二轮发言]
# 李明:[第二轮回应]
# </format>
# """
# )
# )
try:
# 主线程永久挂起(监听退出信号)
while True:

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

Binary file not shown.

View File

@ -0,0 +1,6 @@
#include "Definations.h"
FString FNetCommand::RequestCharacterInfos = TEXT("RequestCharacterInfos");
FString FNetCommand::RequestCharacterNames = TEXT("RequestCharacterNames");
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,29 @@ 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);
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)

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

@ -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)
@ -106,13 +110,28 @@ 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 (!IsValid(WebSocketManager))
{
ModulePtr->InitWebSocketManager();
WebSocketManager = ModulePtr->GetWebSocketManager();
}
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);
}
return EActiveTimerReturnType::Stop;
}
@ -121,60 +140,81 @@ 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");
void SAIGCWindow::HandleReceiveData(TArray<FString> AiMessages)
{
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("生成结束!"));
}
}
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;
//生成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(
FPrompt::StaticStruct(),
&PromptData,
OutputString,
FRequestAIChat::StaticStruct(),
&RequestAIChat,
dataJson,
0,
0
);
return OutputString;
//发送ai对话生成请求
if (ModulePtr)
{
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, dataJson);
}
}
void SAIGCWindow::HandleReceiveData(FNetProtocol protocol)
{
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);
}
}
}

View File

@ -0,0 +1,186 @@
// 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::RequestCharacterInfos, TEXT(""));
}
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SCharacterWindow::HandleReceiveData);
WebSocketManager->OnConnectDelegate.AddLambda([this, WebSocketManager](bool bSuccess)
{
WebSocketManager->SendData(FNetCommand::RequestCharacterInfos, TEXT(""));
});
}
return EActiveTimerReturnType::Stop;
}
void SCharacterWindow::HandleReceiveData(FNetProtocol protocol)
{
if (protocol.cmd == FNetCommand::RequestCharacterInfos)
{
//解析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

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

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,23 @@ 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();
//void OnWebSocketConnect(bool bSuccess);
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

@ -3,46 +3,90 @@
#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 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 FPersonInfo
{
GENERATED_BODY()
FPersonInfo() {}
FPersonInfo(const FString& InName, const FString& InJob):
Name(InName), Job(InJob){}
UPROPERTY()
FString Name;
UPROPERTY()
FString Job;
};
USTRUCT()
struct FPrompt
struct FNetProtocol
{
GENERATED_BODY()
UPROPERTY()
FDialogContent DialogContent;
FString cmd; //命令名称
UPROPERTY()
TArray<FPersonInfo> Persons;
int32 status; //状态
UPROPERTY()
FString message; //响应消息
UPROPERTY()
FString data; //json数据
};
USTRUCT()
struct FAIServerData
struct FNetCommand
{
GENERATED_BODY()
UPROPERTY()
int32 statusCode;
UPROPERTY()
TArray<FString> messages;
};
static FString RequestCharacterInfos;
static FString RequestCharacterNames;
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

@ -3,8 +3,10 @@
#pragma once
#include "CoreMinimal.h"
#include "ConfigItem_ComboBox.h"
#include "ConfigItem_NumberSpin.h"
#include "ConfigItem_Text.h"
#include "Definations.h"
/**
*
@ -22,23 +24,25 @@ public:
void OnAIGenerateClicked();
void InitWebClient();
void HandleReceiveData(TArray<FString> String);
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; //生成按钮
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

@ -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)
{
InputValue = InArgs._MinNumber;
ChildSlot
[
SNew(SHorizontalBox)
@ -53,11 +54,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;