Compare commits
No commits in common. "2984aaa4bed71f1d2b4f0c5a58a65fed38177137" and "9a9675fa6d143df518ae63f3fa26067995a2eff0" have entirely different histories.
2984aaa4be
...
9a9675fa6d
3
.gitignore
vendored
3
.gitignore
vendored
@ -103,6 +103,3 @@ DerivedDataCache/*
|
|||||||
/TestForAIGC/Plugins/HttpHelper/Binaries
|
/TestForAIGC/Plugins/HttpHelper/Binaries
|
||||||
/TestForAIGC/Plugins/HttpHelper/Content
|
/TestForAIGC/Plugins/HttpHelper/Content
|
||||||
/AIGC/aigc.log
|
/AIGC/aigc.log
|
||||||
/TestForAIGC/TestForAIGC.uproject.DotSettings.user
|
|
||||||
/AIGC/__pycache__/AICore.cpython-312.pyc
|
|
||||||
/AIGC/__pycache__/DatabaseHandle.cpython-312.pyc
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
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, "未预料错误"
|
|
@ -1,120 +0,0 @@
|
|||||||
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: 目标角色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 = ?"
|
|
||||||
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
|
chcp 65001 > nul
|
||||||
set OLLAMA_MODEL=deepseek-r1:7b
|
set OLLAMA_MODEL=deepseek-r1:7b
|
||||||
rem 启动Ollama服务
|
rem 启动Ollama服务
|
||||||
start "Ollama DeepSeek" cmd /k ollama serve
|
start "Ollama DeepSeek" cmd /k ollama run %OLLAMA_MODEL%
|
||||||
|
|
||||||
rem 检测11434端口是否就绪
|
rem 检测11434端口是否就绪
|
||||||
echo 等待Ollama服务启动...
|
echo 等待Ollama服务启动...
|
||||||
|
BIN
AIGC/data.db
BIN
AIGC/data.db
Binary file not shown.
382
AIGC/main.py
382
AIGC/main.py
@ -11,10 +11,8 @@ from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconn
|
|||||||
from fastapi.websockets import WebSocketState
|
from fastapi.websockets import WebSocketState
|
||||||
from h11 import ConnectionClosed
|
from h11 import ConnectionClosed
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from AICore import AICore
|
|
||||||
from DatabaseHandle import DatabaseHandle
|
|
||||||
from Utils.AIGCLog import AIGCLog
|
from Utils.AIGCLog import AIGCLog
|
||||||
|
from ollama import Client, ResponseError
|
||||||
|
|
||||||
app = FastAPI(title = "AI 通信服务")
|
app = FastAPI(title = "AI 通信服务")
|
||||||
logger = AIGCLog(name = "AIGC", log_file = "aigc.log")
|
logger = AIGCLog(name = "AIGC", log_file = "aigc.log")
|
||||||
@ -26,15 +24,11 @@ parser.add_argument('--model', type=str, default='deepseek-r1:1.5b',
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logger.log(logging.INFO, f"使用的模型是 {args.model}")
|
logger.log(logging.INFO, f"使用的模型是 {args.model}")
|
||||||
|
|
||||||
maxAIRegerateCount = 5 #最大重新生成次数
|
maxAIRegerateCount = 5
|
||||||
regenerateCount = 1 #当前重新生成次数
|
|
||||||
totalAIGenerateCount = 1 #客户端生成AI响应总数
|
|
||||||
currentGenerateCount = 0 #当前生成次数
|
|
||||||
lastPrompt = ""
|
lastPrompt = ""
|
||||||
character_id1 = 0
|
|
||||||
character_id2 = 0
|
#初始化ollama客户端
|
||||||
aicore = AICore(args.model)
|
ollamaClient = Client(host='http://localhost:11434')
|
||||||
database = DatabaseHandle()
|
|
||||||
|
|
||||||
async def heartbeat(websocket: WebSocket):
|
async def heartbeat(websocket: WebSocket):
|
||||||
pass
|
pass
|
||||||
@ -46,22 +40,14 @@ async def heartbeat(websocket: WebSocket):
|
|||||||
# break # 连接已关闭时退出循环
|
# break # 连接已关闭时退出循环
|
||||||
|
|
||||||
#statuscode -1 服务器运行错误 0 心跳标志 1 正常 2 输出异常
|
#statuscode -1 服务器运行错误 0 心跳标志 1 正常 2 输出异常
|
||||||
async def senddata(websocket: WebSocket, protocol: dict):
|
async def senddata(websocket: WebSocket, statusCode: int, messages: List[str]):
|
||||||
# 将AI响应发送回UE5
|
# 将AI响应发送回UE5
|
||||||
if websocket.client_state == WebSocketState.CONNECTED:
|
if websocket.client_state == WebSocketState.CONNECTED:
|
||||||
json_string = json.dumps(protocol, ensure_ascii=False)
|
data = {
|
||||||
await websocket.send_text(json_string)
|
"statuscode": statusCode,
|
||||||
|
"messages": messages
|
||||||
async def sendprotocol(websocket: WebSocket, cmd: str, status: int, message: str, data: str):
|
}
|
||||||
# 将AI响应发送回UE5
|
json_string = json.dumps(data, ensure_ascii=False)
|
||||||
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)
|
await websocket.send_text(json_string)
|
||||||
|
|
||||||
# WebSocket路由处理
|
# WebSocket路由处理
|
||||||
@ -77,8 +63,19 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
|||||||
# 接收UE5发来的消息
|
# 接收UE5发来的消息
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
logger.log(logging.INFO, f"收到UE5消息 [{client_id}]: {data}")
|
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:
|
except WebSocketDisconnect:
|
||||||
#manager.disconnect(client_id)
|
#manager.disconnect(client_id)
|
||||||
logger.log(logging.WARNING, f"UE5客户端主动断开 [{client_id}]")
|
logger.log(logging.WARNING, f"UE5客户端主动断开 [{client_id}]")
|
||||||
@ -86,185 +83,45 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
|||||||
#manager.disconnect(client_id)
|
#manager.disconnect(client_id)
|
||||||
logger.log(logging.ERROR, f"WebSocket异常 [{client_id}]: {str(e)}")
|
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]:
|
def process_prompt(promptFromUE: str) -> Tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
data = json.loads(promptFromUE)
|
data = json.loads(promptFromUE)
|
||||||
global maxAIRegerateCount
|
|
||||||
# 提取数据
|
# 提取数据
|
||||||
dialog_scene = data["dialogScene"]
|
dialog_scene = data["dialogContent"]["dialogScene"]
|
||||||
global totalAIGenerateCount
|
persons = data["persons"]
|
||||||
totalAIGenerateCount = data["generateCount"]
|
|
||||||
persons = data["characterName"]
|
|
||||||
|
|
||||||
assert len(persons) == 2
|
assert len(persons) == 2
|
||||||
characterInfo1 = database.get_character_byname(persons[0])
|
for person in persons:
|
||||||
characterInfo2 = database.get_character_byname(persons[1])
|
print(f" 姓名: {person['name']}, 职业: {person['job']}")
|
||||||
global character_id1, character_id2
|
|
||||||
character_id1 = characterInfo1[0]["id"]
|
|
||||||
character_id2 = characterInfo2[0]["id"]
|
|
||||||
chat_history = database.get_chats_by_character_id(str(character_id1) + "," + str(character_id2))
|
|
||||||
#整理对话记录
|
|
||||||
result = result = '\n'.join([item['chat'] for item in chat_history])
|
|
||||||
|
|
||||||
|
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
#你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
|
你是一个游戏NPC对话生成器。请严格按以下要求生成两个路人NPC({persons[0]["name"]}和{persons[1]["name"]})的日常对话:
|
||||||
#对话的背景
|
1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
||||||
{dialog_scene}
|
2. 对话场景:{dialog_scene}
|
||||||
1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
3. 角色设定:
|
||||||
2.角色设定
|
{persons[0]["name"]}:{persons[0]["job"]}
|
||||||
{characterInfo1[0]["name"]}: {{
|
{persons[1]["name"]}:{persons[1]["job"]}
|
||||||
"姓名": {characterInfo1[0]["name"]},
|
4. 对话要求:
|
||||||
"年龄": {characterInfo1[0]["age"]},
|
* 每轮对话需自然衔接,体现生活细节
|
||||||
"性格": {characterInfo1[0]["personality"]},
|
* 避免任务指引或玩家交互内容
|
||||||
"职业": {characterInfo1[0]["profession"]},
|
* 结尾保持对话未完成感
|
||||||
"背景": {characterInfo1[0]["characterBackground"]},
|
5. 输出格式:
|
||||||
"语言风格": {characterInfo1[0]["chat_style"]}
|
<format>
|
||||||
}},
|
{persons[0]["name"]}:[第一轮发言]
|
||||||
{characterInfo2[0]["name"]}: {{
|
{persons[1]["name"]}:[第一轮回应]
|
||||||
"姓名": {characterInfo2[0]["name"]},
|
{persons[0]["name"]}:[第二轮发言]
|
||||||
"年龄": {characterInfo2[0]["age"]},
|
{persons[1]["name"]}:[第二轮回应]
|
||||||
"性格": {characterInfo2[0]["personality"]},
|
</format>
|
||||||
"职业": {characterInfo2[0]["profession"]},
|
|
||||||
"背景": {characterInfo2[0]["characterBackground"]},
|
6.重要!若未按此格式输出,请重新生成直至完全符合
|
||||||
"语言风格": {characterInfo2[0]["chat_style"]}
|
|
||||||
}}
|
|
||||||
3.参考的历史对话内容
|
|
||||||
{result}
|
|
||||||
4.输出格式:
|
|
||||||
<format>
|
|
||||||
张三:[第一轮发言]
|
|
||||||
李明:[第一轮回应]
|
|
||||||
张三:[第二轮发言]
|
|
||||||
李明:[第二轮回应]
|
|
||||||
</format>
|
|
||||||
5.重要!若未按此格式输出,请重新生成直至完全符合
|
|
||||||
"""
|
"""
|
||||||
return True, prompt
|
return True, prompt
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"JSON解析错误: {e}")
|
print(f"JSON解析错误: {e}")
|
||||||
return False, ""
|
return False, ""
|
||||||
except Exception as e:
|
except KeyError as e:
|
||||||
print(f"发生错误:{type(e).__name__} - {e}")
|
print(f"缺少必要字段: {e}")
|
||||||
return False, ""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -281,15 +138,76 @@ def run_webserver():
|
|||||||
log_level="info"
|
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):
|
async def reGenerateAIChat(prompt: str, websocket: WebSocket):
|
||||||
global regenerateCount
|
global regenerateCount
|
||||||
if regenerateCount < maxAIRegerateCount:
|
if regenerateCount < maxAIRegerateCount:
|
||||||
regenerateCount += 1
|
regenerateCount += 1
|
||||||
logger.log(logging.ERROR, f"AI输出格式不正确,重新进行生成 {regenerateCount}/{maxAIRegerateCount}")
|
logger.log(logging.ERROR, f"AI输出格式不正确,重新进行生成 {regenerateCount}/{maxAIRegerateCount}")
|
||||||
await sendprotocol(websocket, "AiChatGenerate", 0, "ai生成格式不正确, 重新进行生成", "")
|
await senddata(websocket, 2, messages=["ai生成格式不正确, 重新进行生成"])
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
prompt = prompt + "补充:上一次的输出格式错误,严格执行prompt中的输出格式要求"
|
prompt = prompt + "补充:上一次的输出格式错误,严格执行prompt中第5条的输出格式要求"
|
||||||
await generate_aichat(prompt, websocket)
|
await generateAIChat(prompt, websocket)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
regenerateCount = 0
|
regenerateCount = 0
|
||||||
@ -302,57 +220,29 @@ if __name__ == "__main__":
|
|||||||
server_thread.daemon = True # 设为守护线程(主程序退出时自动终止)
|
server_thread.daemon = True # 设为守护线程(主程序退出时自动终止)
|
||||||
server_thread.start()
|
server_thread.start()
|
||||||
|
|
||||||
|
## Test
|
||||||
#Test database
|
# generateAIChat(f"""
|
||||||
|
# 你是一个游戏NPC对话生成器。请严格按以下要求生成两个路人NPC(A和B)的日常对话:
|
||||||
|
# 1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
||||||
|
# 2. 对话场景:中世纪奇幻小镇的日常场景(如市场/酒馆/街道)
|
||||||
# id = database.add_character({"name":"李明","age":30,"personality":"活泼健谈","profession":"产品经理"
|
# 3. 角色设定:
|
||||||
# ,"characterBackground":"公司资深产品经理","chat_style":"热情"})
|
# - NPC A:随机职业(铁匠/农夫/商人/卫兵等)
|
||||||
|
# - NPC B:随机职业(不同于A)
|
||||||
#characters = database.get_character_byname("")
|
# 4. 对话要求:
|
||||||
#chat_id = database.add_chat({"character_ids":"1,2","chat":"张三:[第一轮发言] 李明:[第一轮回应] 张三:[第二轮发言] 李明:[第二轮回应"})
|
# * 每轮对话需自然衔接,体现生活细节
|
||||||
#chat = database.get_chats_by_character_id(3)
|
# * 避免任务指引或玩家交互内容
|
||||||
#
|
# * 结尾保持对话未完成感
|
||||||
# Test AI
|
# 5. 输出格式(严格遵循,):
|
||||||
#aicore.getPromptToken("测试功能")
|
# ---
|
||||||
# asyncio.run(
|
# A:[第一轮发言]
|
||||||
# generateAIChat(promptStr = f"""
|
# B:[第一轮回应]
|
||||||
# #你是一个游戏NPC对话生成器。请严格按以下要求生成两个角色的日常对话
|
# A:[第二轮发言]
|
||||||
# #对话的世界观背景是2025年的都市背景
|
# B:[第二轮回应]
|
||||||
# 1. 生成【2轮完整对话】,每轮包含双方各一次发言(共4句)
|
# ---
|
||||||
# 2.角色设定
|
# """
|
||||||
|
|
||||||
# "张三": {{
|
|
||||||
# "姓名": "张三",
|
|
||||||
# "年龄": 35,
|
|
||||||
# "性格": "成熟稳重/惜字如金",
|
|
||||||
# "职业": "阿里巴巴算法工程师",
|
|
||||||
# "背景": "浙大计算机系毕业,专注AI优化项目",
|
|
||||||
# "对话场景": "你正在和用户聊天,用户是你的同事",
|
|
||||||
# "语言风格": "请在对话中表现出专业、冷静、惜字如金。用口语化的方式简短回答"
|
|
||||||
# }},
|
|
||||||
# "李明": {{
|
|
||||||
# "姓名": "李明",
|
|
||||||
# "年龄": 30,
|
|
||||||
# "职业": "产品经理",
|
|
||||||
# "性格": "活泼健谈"
|
|
||||||
# "背景": "公司资深产品经理",
|
|
||||||
# "对话场景": "你正在和用户聊天,用户是你的同事",
|
|
||||||
# "语言风格": "热情"
|
|
||||||
# }}
|
|
||||||
|
|
||||||
# 3.输出格式:
|
|
||||||
# <format>
|
|
||||||
# 张三:[第一轮发言]
|
|
||||||
# 李明:[第一轮回应]
|
|
||||||
# 张三:[第二轮发言]
|
|
||||||
# 李明:[第二轮回应]
|
|
||||||
# </format>
|
|
||||||
# """
|
|
||||||
# )
|
|
||||||
# )
|
# )
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 主线程永久挂起(监听退出信号)
|
# 主线程永久挂起(监听退出信号)
|
||||||
while True:
|
while True:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
fastapi
|
fastapi
|
||||||
ollama
|
ollama
|
||||||
tiktoken
|
|
@ -1,2 +0,0 @@
|
|||||||
[Paths]
|
|
||||||
Prefix=..
|
|
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
[/Script/AIGC.AIGCSetting]
|
|
||||||
ServerIP=127.0.0.1
|
|
||||||
|
|
BIN
TestForAIGC/Content/NewWidgetBlueprint.uasset
Normal file
BIN
TestForAIGC/Content/NewWidgetBlueprint.uasset
Normal file
Binary file not shown.
BIN
TestForAIGC/Content/q.uasset
Normal file
BIN
TestForAIGC/Content/q.uasset
Normal file
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
#include "Definations.h"
|
|
||||||
|
|
||||||
FString FNetCommand::RequestCharacterInfos = TEXT("RequestCharacterInfos");
|
|
||||||
FString FNetCommand::RequestCharacterNames = TEXT("RequestCharacterNames");
|
|
||||||
FString FNetCommand::AddCharacter = TEXT("AddCharacter");
|
|
||||||
FString FNetCommand::AiChatGenerate = TEXT("AiChatGenerate");
|
|
@ -2,34 +2,42 @@
|
|||||||
|
|
||||||
#include "AIGC.h"
|
#include "AIGC.h"
|
||||||
|
|
||||||
#include "AIGCSetting.h"
|
|
||||||
#include "AIGCStyle.h"
|
#include "AIGCStyle.h"
|
||||||
#include "AssetToolsModule.h"
|
#include "AssetToolsModule.h"
|
||||||
#include "FileHelpers.h"
|
#include "FileHelpers.h"
|
||||||
#include "ISettingsModule.h"
|
|
||||||
#include "LevelEditor.h"
|
#include "LevelEditor.h"
|
||||||
#include "ToolMenu.h"
|
#include "ToolMenu.h"
|
||||||
#include "ToolMenus.h"
|
#include "ToolMenus.h"
|
||||||
#include "WebSocketManager.h"
|
|
||||||
#include "AssetRegistry/AssetRegistryModule.h"
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||||||
#include "Misc/MessageDialog.h"
|
#include "Misc/MessageDialog.h"
|
||||||
#include "UObject/SavePackage.h"
|
#include "UObject/SavePackage.h"
|
||||||
#include "Widget/AIGCWindow.h"
|
#include "Widget/AIGCWindow.h"
|
||||||
#include "Widget/CharacterWindow.h"
|
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FAIGCModule"
|
#define LOCTEXT_NAMESPACE "FAIGCModule"
|
||||||
#define AIGCWindowName FName("AIGC")
|
#define AIGCWindowName FName("AIGC")
|
||||||
#define CharacterWindowName FName("Character")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void FAIGCModule::StartupModule()
|
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
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
RegeisterSettings();
|
|
||||||
//添加style
|
//添加style
|
||||||
FAIGCStyle::Initialize();
|
FAIGCStyle::Initialize();
|
||||||
FAIGCStyle::ReloadTextures();
|
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"));
|
UToolMenu* menu = UToolMenus::Get()->FindMenu(FName("LevelEditor.MainMenu.AIGC"));
|
||||||
if (!menu)
|
if (!menu)
|
||||||
@ -54,17 +62,16 @@ void FAIGCModule::StartupModule()
|
|||||||
FGlobalTabmanager::Get()->TryInvokeTab(AIGCWindowName);
|
FGlobalTabmanager::Get()->TryInvokeTab(AIGCWindowName);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
//添加人员配置菜单
|
// // 填充第二个子菜单项
|
||||||
FToolMenuSection& CharacterSection = SubMenu->AddSection("Character");
|
// FToolMenuSection& SubSection2 = SubMenu->AddSection(
|
||||||
SubSection.AddMenuEntry(
|
// "SubSection2");
|
||||||
"Character",
|
// SubSection.AddMenuEntry(
|
||||||
LOCTEXT("Character", "角色配置"),
|
// "Submenu2",
|
||||||
LOCTEXT("CharacterToolTip", ""),
|
// LOCTEXT("Action1Label", "生成模型2"),
|
||||||
FSlateIcon(FAIGCStyle::GetStyleSetName(), "AIGCStyle.AIChat"),
|
// LOCTEXT("Action1Tooltip", "触发模型生成2"),
|
||||||
FUIAction(FExecuteAction::CreateLambda([this]()
|
// FSlateIcon(FAIGCStyle::GetStyleSetName(), "AIGCStyle.AIChat"),
|
||||||
{
|
// FToolUIActionChoice(
|
||||||
FGlobalTabmanager::Get()->TryInvokeTab(CharacterWindowName);
|
// FAIGCCommands::Get().SubMenuAction, *PluginCommands));
|
||||||
})));
|
|
||||||
}),
|
}),
|
||||||
false, // 是否动态生成
|
false, // 是否动态生成
|
||||||
FSlateIcon());
|
FSlateIcon());
|
||||||
@ -74,54 +81,42 @@ void FAIGCModule::StartupModule()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//注册Chat窗口
|
//注册窗口
|
||||||
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(AIGCWindowName, FOnSpawnTab::CreateRaw(this, &FAIGCModule::OnSpawnAIChatTab))
|
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(AIGCWindowName, FOnSpawnTab::CreateRaw(this, &FAIGCModule::OnSpawnPluginTab))
|
||||||
.SetDisplayName(LOCTEXT("AIGCWindow", "AIGCWindow"))
|
.SetDisplayName(LOCTEXT("AIGCWindow", "AIGCWindow"))
|
||||||
.SetMenuType(ETabSpawnerMenuType::Hidden);
|
.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()
|
void FAIGCModule::ShutdownModule()
|
||||||
{
|
{
|
||||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
// 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.
|
// we call this function before unloading the module.
|
||||||
UnregisterSettings();
|
|
||||||
UToolMenus::UnregisterOwner(this);
|
UToolMenus::UnregisterOwner(this);
|
||||||
FAIGCStyle::Shutdown();
|
FAIGCStyle::Shutdown();
|
||||||
|
//FAIGCCommands::Unregister();
|
||||||
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(AIGCWindowName);
|
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::OnSpawnAIChatTab(const FSpawnTabArgs& SpawnTabArgs)
|
|
||||||
|
TSharedRef<SDockTab> FAIGCModule::OnSpawnPluginTab(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)
|
return SNew(SDockTab)
|
||||||
.TabRole(ETabRole::NomadTab)
|
.TabRole(ETabRole::NomadTab)
|
||||||
[
|
[
|
||||||
@ -135,21 +130,6 @@ TSharedRef<SDockTab> FAIGCModule::OnSpawnAIChatTab(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)
|
void FAIGCModule::CreateOrAddData(const FString& DataTablePath, const FString& RowValue)
|
||||||
{
|
{
|
||||||
FString PackagePath, AssetName;
|
FString PackagePath, AssetName;
|
||||||
@ -178,29 +158,6 @@ void FAIGCModule::CreateOrAddData(const FString& DataTablePath, const FString& R
|
|||||||
UPackage::SavePackage(DataTable->GetPackage(), DataTable, *FilePath, SaveArgs);
|
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
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
IMPLEMENT_MODULE(FAIGCModule, AIGC)
|
IMPLEMENT_MODULE(FAIGCModule, AIGC)
|
@ -1,4 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "AIGCSetting.h"
|
|
@ -19,16 +19,10 @@ void UWebSocketManager::InitWebSocket(const FString& ServerIP)
|
|||||||
,*ServerIP, *(FGuid::NewGuid().ToString()) )));
|
,*ServerIP, *(FGuid::NewGuid().ToString()) )));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UWebSocketManager::SendData(const FString& cmd, const FString& Data)
|
void UWebSocketManager::SendData(const FString& Data)
|
||||||
{
|
{
|
||||||
//定义客户端协议
|
|
||||||
FNetProtocol protocol;
|
|
||||||
protocol.cmd = cmd;
|
|
||||||
protocol.data = Data;
|
|
||||||
FString JsonString;
|
|
||||||
FJsonObjectConverter::UStructToJsonObjectString(protocol, JsonString);
|
|
||||||
|
|
||||||
WebSocketHandler->SendText(JsonString);
|
WebSocketHandler->SendText(Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UWebSocketManager::HandleConnected()
|
void UWebSocketManager::HandleConnected()
|
||||||
@ -58,38 +52,30 @@ void UWebSocketManager::HandleOnMessageSend(const FString& Message)
|
|||||||
void UWebSocketManager::HandleOnReceiveText(const FString& Message)
|
void UWebSocketManager::HandleOnReceiveText(const FString& Message)
|
||||||
{
|
{
|
||||||
|
|
||||||
// FAIServerData ServerData;
|
FAIServerData ServerData;
|
||||||
// FJsonObjectConverter::JsonObjectStringToUStruct<FAIServerData>(
|
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,
|
Message,
|
||||||
&serverProtocol,
|
&ServerData,
|
||||||
0, // 检查标志位
|
0, // 检查标志位
|
||||||
0 // 转换标志位
|
0 // 转换标志位
|
||||||
);
|
);
|
||||||
OnDataReceiveDelaget.Broadcast(serverProtocol);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,15 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
|
|||||||
{
|
{
|
||||||
ChildSlot[
|
ChildSlot[
|
||||||
SNew(SVerticalBox)
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(10)
|
||||||
|
[
|
||||||
|
SAssignNew(ServerIP, SConfigItem_Text)
|
||||||
|
.Title(LOCTEXT("Server", "服务器ip"))
|
||||||
|
.HintText(LOCTEXT("HintServer", "请输入服务器ip"))
|
||||||
|
.DefaultText(LOCTEXT("InputServer", "127.0.0.1"))
|
||||||
|
]
|
||||||
+ SVerticalBox::Slot()
|
+ SVerticalBox::Slot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
@ -27,48 +36,35 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
|
|||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
[
|
[
|
||||||
SAssignNew(NPCName1, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
|
SAssignNew(NPCName1, SConfigItem_Text)
|
||||||
.Title(LOCTEXT("Name1", "名称1"))
|
.Title(LOCTEXT("Name1", "名称1"))
|
||||||
.OptionsSource(&CharacterInfos)
|
.HintText(LOCTEXT("HintName", "请输入名称"))
|
||||||
.Content()
|
|
||||||
[
|
|
||||||
SNew(STextBlock)
|
|
||||||
.Text_Lambda([this]() -> FText {
|
|
||||||
|
|
||||||
return FText::FromString(SelectedCharacter1.name);
|
|
||||||
})
|
|
||||||
]
|
|
||||||
.OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
|
|
||||||
return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
|
|
||||||
})
|
|
||||||
.OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
|
|
||||||
{
|
|
||||||
SelectedCharacter1 = *CharacterInfo;
|
|
||||||
})
|
|
||||||
]
|
]
|
||||||
+ SVerticalBox::Slot()
|
+ SVerticalBox::Slot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
[
|
[
|
||||||
SAssignNew(NPCName2, SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>)
|
SAssignNew(NPCJob1, SConfigItem_Text)
|
||||||
.Title(LOCTEXT("Name2", "名称2"))
|
.Title(LOCTEXT("Job1", "职业1"))
|
||||||
.OptionsSource(&CharacterInfos)
|
.HintText(LOCTEXT("HintJon", "请输入职业"))
|
||||||
.Content()
|
]
|
||||||
[
|
+ SVerticalBox::Slot()
|
||||||
SNew(STextBlock)
|
.AutoHeight()
|
||||||
.Text_Lambda([this]() -> FText {
|
.Padding(10)
|
||||||
|
[
|
||||||
return FText::FromString(SelectedCharacter2.name);
|
SAssignNew(NPCName2, SConfigItem_Text)
|
||||||
})
|
.Title(LOCTEXT("Name2", "名称2"))
|
||||||
]
|
.HintText(LOCTEXT("HintName", "请输入名称"))
|
||||||
.OnGenerateWidget_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo) {
|
]
|
||||||
return SNew(STextBlock).Text(FText::FromString(CharacterInfo->name));
|
|
||||||
})
|
+ SVerticalBox::Slot()
|
||||||
.OnSelectionChanged_Lambda([this](TSharedPtr<FCharacterInfo> CharacterInfo, ESelectInfo::Type)
|
.AutoHeight()
|
||||||
{
|
.Padding(10)
|
||||||
SelectedCharacter2 = *CharacterInfo;
|
[
|
||||||
})
|
SAssignNew(NPCJob2, SConfigItem_Text)
|
||||||
]
|
.Title(LOCTEXT("Job2", "职业2"))
|
||||||
|
.HintText(LOCTEXT("HintJon", "请输入职业"))
|
||||||
|
]
|
||||||
+ SVerticalBox::Slot()
|
+ SVerticalBox::Slot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
@ -110,28 +106,13 @@ void SAIGCWindow::Construct(const FArguments& InArgs)
|
|||||||
|
|
||||||
EActiveTimerReturnType SAIGCWindow::OnPostPaint(double X, float Arg)
|
EActiveTimerReturnType SAIGCWindow::OnPostPaint(double X, float Arg)
|
||||||
{
|
{
|
||||||
//init websocket
|
WebSocketManager = NewObject<UWebSocketManager>();
|
||||||
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
|
WebSocketManager->InitWebSocket(*ServerIP->GetInputText());
|
||||||
if (ModulePtr)
|
WebSocketManager->OnConnectDelegate.AddLambda([this](bool bSuccess)
|
||||||
{
|
{
|
||||||
UWebSocketManager* WebSocketManager = ModulePtr->GetWebSocketManager();
|
GenerateButton->SetEnabled(bSuccess);
|
||||||
if (!IsValid(WebSocketManager))
|
});
|
||||||
{
|
WebSocketManager->OnDataReceiveDelaget.AddRaw(this, &SAIGCWindow::HandleReceiveData);
|
||||||
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;
|
return EActiveTimerReturnType::Stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,81 +121,60 @@ void SAIGCWindow::OnAIGenerateClicked()
|
|||||||
{
|
{
|
||||||
GenerateButton->SetEnabled(false);
|
GenerateButton->SetEnabled(false);
|
||||||
RequireGenerateCount = GenerateCount->GetNumber();
|
RequireGenerateCount = GenerateCount->GetNumber();
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("生成次数 %d prompt:%s"), RequireGenerateCount, *GeneratePromptJson());
|
||||||
|
WebSocketManager->SendData(GeneratePromptJson());
|
||||||
|
}
|
||||||
|
|
||||||
FAIGCModule* ModulePtr = FModuleManager::GetModulePtr<FAIGCModule>("AIGC");
|
|
||||||
|
|
||||||
//生成ai 配置信息
|
void SAIGCWindow::HandleReceiveData(TArray<FString> AiMessages)
|
||||||
FRequestAIChat RequestAIChat;
|
{
|
||||||
RequestAIChat.DialogScene =DialogScene->GetInputText();
|
UE_LOG(LogTemp, Warning, TEXT("AI当前进度 %d/%d"), GeneratedCount, RequireGenerateCount);
|
||||||
RequestAIChat.GenerateCount = GenerateCount->GetNumber();
|
//添加数据到dataTable中
|
||||||
RequestAIChat.CharacterName.Add(SelectedCharacter1.name);
|
if (AiMessages.Num() > 0)
|
||||||
RequestAIChat.CharacterName.Add(SelectedCharacter2.name);
|
|
||||||
FString dataJson;
|
|
||||||
FJsonObjectConverter::UStructToJsonObjectString(
|
|
||||||
FRequestAIChat::StaticStruct(),
|
|
||||||
&RequestAIChat,
|
|
||||||
dataJson,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
//发送ai对话生成请求
|
|
||||||
if (ModulePtr)
|
|
||||||
{
|
{
|
||||||
ModulePtr->GetWebSocketManager()->SendData(FNetCommand::AiChatGenerate, dataJson);
|
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()
|
||||||
void SAIGCWindow::HandleReceiveData(FNetProtocol protocol)
|
|
||||||
{
|
{
|
||||||
if (protocol.cmd == FNetCommand::RequestCharacterNames)
|
FPrompt PromptData;
|
||||||
{
|
PromptData.DialogContent = FDialogContent(DialogScene->GetInputText());
|
||||||
|
PromptData.Persons.Add(FPersonInfo(NPCName1->GetInputText(), NPCJob1->GetInputText()));
|
||||||
//解析json角色信息
|
PromptData.Persons.Add(FPersonInfo(NPCName2->GetInputText(), NPCJob2->GetInputText()));
|
||||||
FJsonObjectConverter::JsonObjectStringToUStruct<FCharacterArray>(
|
// 结构体转JSON字符串
|
||||||
protocol.data,
|
FString OutputString;
|
||||||
&Characters,
|
FJsonObjectConverter::UStructToJsonObjectString(
|
||||||
0, // 检查标志位
|
FPrompt::StaticStruct(),
|
||||||
0 // 转换标志位
|
&PromptData,
|
||||||
);
|
OutputString,
|
||||||
CharacterInfos.Empty();
|
0,
|
||||||
for (auto Character: Characters.characterInfos)
|
0
|
||||||
{
|
);
|
||||||
CharacterInfos.Add(MakeShareable(new FCharacterInfo(Character)));
|
return OutputString;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
// 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
|
|
@ -1,5 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "Widget/ConfigItem_ComboBox.h"
|
|
||||||
|
|
@ -23,7 +23,6 @@ void SConfigItem_Text::Construct(const FArguments& InArgs)
|
|||||||
[
|
[
|
||||||
SNew(SEditableTextBox)
|
SNew(SEditableTextBox)
|
||||||
.HintText( InArgs._HintText)
|
.HintText( InArgs._HintText)
|
||||||
.VirtualKeyboardType(InArgs._KeyboardType)
|
|
||||||
.Text_Lambda([this, InArgs]() {
|
.Text_Lambda([this, InArgs]() {
|
||||||
return FText::FromString(InputText);
|
return FText::FromString(InputText);
|
||||||
})
|
})
|
||||||
|
@ -37,23 +37,13 @@ struct FNPCChatRow : public FTableRowBase {
|
|||||||
class FAIGCModule : public IModuleInterface
|
class FAIGCModule : public IModuleInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TSharedRef<SDockTab> OnSpawnAIChatTab(const FSpawnTabArgs& SpawnTabArgs);
|
TSharedRef<SDockTab> OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs);
|
||||||
TSharedRef<SDockTab> OnSpawnCharacterTab(const FSpawnTabArgs& SpawnTabArgs);
|
|
||||||
//void SubMenuClicked();
|
//void SubMenuClicked();
|
||||||
/** IModuleInterface implementation */
|
/** IModuleInterface implementation */
|
||||||
virtual void StartupModule() override;
|
virtual void StartupModule() override;
|
||||||
virtual void ShutdownModule() override;
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
void RegeisterSettings();
|
|
||||||
void UnregisterSettings();
|
|
||||||
|
|
||||||
void CreateOrAddData(const FString& DataTablePath, const FString& RowValue);
|
void CreateOrAddData(const FString& DataTablePath, const FString& RowValue);
|
||||||
class UWebSocketManager* GetWebSocketManager();
|
|
||||||
void InitWebSocketManager();
|
|
||||||
//void OnWebSocketConnect(bool bSuccess);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TSharedPtr<class FUICommandList> PluginCommands;
|
TSharedPtr<class FUICommandList> PluginCommands;
|
||||||
class UWebSocketManager* WebSocketManager = nullptr;
|
|
||||||
};
|
};
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
@ -1,19 +0,0 @@
|
|||||||
// 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");
|
|
||||||
};
|
|
@ -3,90 +3,46 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Definations.generated.h"
|
#include "Definations.generated.h"
|
||||||
|
|
||||||
// USTRUCT()
|
|
||||||
// struct FDialogContent
|
|
||||||
// {
|
|
||||||
// GENERATED_BODY()
|
|
||||||
// FDialogContent() {};
|
|
||||||
//
|
|
||||||
// FDialogContent(const FString& InDialogScene) : DialogScene(InDialogScene) {}
|
|
||||||
// UPROPERTY()
|
|
||||||
// FString DialogScene;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// USTRUCT()
|
|
||||||
// struct FPersonInfo
|
|
||||||
// {
|
|
||||||
// GENERATED_BODY()
|
|
||||||
// FPersonInfo() {}
|
|
||||||
// FPersonInfo(const FString& InName, const FString& InJob):
|
|
||||||
// Name(InName), Job(InJob){}
|
|
||||||
// UPROPERTY()
|
|
||||||
// FString Name;
|
|
||||||
// UPROPERTY()
|
|
||||||
// FString Job;
|
|
||||||
//
|
|
||||||
// };
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FRequestAIChat
|
struct FDialogContent
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
FDialogContent() {};
|
||||||
|
|
||||||
|
FDialogContent(const FString& InDialogScene) : DialogScene(InDialogScene) {}
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString DialogScene;
|
FString DialogScene;
|
||||||
UPROPERTY()
|
|
||||||
int32 GenerateCount;
|
|
||||||
UPROPERTY()
|
|
||||||
TArray<FString> CharacterName;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
USTRUCT()
|
|
||||||
struct 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()
|
USTRUCT()
|
||||||
struct FNetProtocol
|
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()
|
GENERATED_BODY()
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString cmd; //命令名称
|
FDialogContent DialogContent;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int32 status; //状态
|
TArray<FPersonInfo> Persons;
|
||||||
UPROPERTY()
|
|
||||||
FString message; //响应消息
|
|
||||||
UPROPERTY()
|
|
||||||
FString data; //json数据
|
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FNetCommand
|
struct FAIServerData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
static FString RequestCharacterInfos;
|
UPROPERTY()
|
||||||
static FString RequestCharacterNames;
|
int32 statusCode;
|
||||||
static FString AddCharacter;
|
UPROPERTY()
|
||||||
static FString AiChatGenerate;
|
TArray<FString> messages;
|
||||||
};
|
};
|
@ -3,12 +3,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Definations.h"
|
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "WebSocketManager.generated.h"
|
#include "WebSocketManager.generated.h"
|
||||||
|
|
||||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnConnectDelegate, bool);
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnConnectDelegate, bool);
|
||||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDataReceiveDelaget, FNetProtocol);
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDataReceiveDelaget, TArray<FString>);
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -18,7 +17,7 @@ class AIGC_API UWebSocketManager : public UObject
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
public:
|
public:
|
||||||
void InitWebSocket(const FString& ServerIP);
|
void InitWebSocket(const FString& ServerIP);
|
||||||
void SendData(const FString& cmd, const FString& Data);
|
void SendData(const FString& Data);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleConnected();
|
void HandleConnected();
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
|
@ -3,10 +3,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "ConfigItem_ComboBox.h"
|
|
||||||
#include "ConfigItem_NumberSpin.h"
|
#include "ConfigItem_NumberSpin.h"
|
||||||
#include "ConfigItem_Text.h"
|
#include "ConfigItem_Text.h"
|
||||||
#include "Definations.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -24,25 +22,23 @@ public:
|
|||||||
void OnAIGenerateClicked();
|
void OnAIGenerateClicked();
|
||||||
|
|
||||||
void InitWebClient();
|
void InitWebClient();
|
||||||
void HandleReceiveData(FNetProtocol protocol);
|
void HandleReceiveData(TArray<FString> String);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
FString GeneratePromptJson();
|
||||||
int32 RequireGenerateCount;
|
int32 RequireGenerateCount;
|
||||||
int32 GeneratedCount = 1;
|
int32 GeneratedCount = 1;
|
||||||
FCharacterArray Characters;
|
|
||||||
TArray<TSharedPtr<FCharacterInfo>> CharacterInfos;
|
|
||||||
FCharacterInfo SelectedCharacter1;
|
|
||||||
FCharacterInfo SelectedCharacter2;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
TSharedPtr<SConfigItem_Text> ServerIP; //服务器IP
|
||||||
TSharedPtr<SConfigItem_Text> DataName; //datatable 名称
|
TSharedPtr<SConfigItem_Text> DataName; //datatable 名称
|
||||||
TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName1; //npc1的名称
|
TSharedPtr<SConfigItem_Text> NPCName1; //npc1的名称
|
||||||
TSharedPtr<SConfigItem_ComboBox<TSharedPtr<FCharacterInfo>>> NPCName2; //npc2的名称
|
TSharedPtr<SConfigItem_Text> NPCJob1; //npc1的职业
|
||||||
|
TSharedPtr<SConfigItem_Text> NPCName2; //npc2的名称
|
||||||
|
TSharedPtr<SConfigItem_Text> NPCJob2; //npc2的职业
|
||||||
TSharedPtr<SConfigItem_Text> DialogScene; //对话场景
|
TSharedPtr<SConfigItem_Text> DialogScene; //对话场景
|
||||||
TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目
|
TSharedPtr<SConfigItem_NumberSpin<int32>> GenerateCount; //生成数目
|
||||||
TSharedPtr<SButton> GenerateButton; //生成按钮
|
TSharedPtr<SButton> GenerateButton; //生成按钮
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
class UWebSocketManager* WebSocketManager;
|
||||||
};
|
};
|
@ -1,40 +0,0 @@
|
|||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
|||||||
// 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,7 +25,6 @@ public:
|
|||||||
|
|
||||||
void Construct(const FArguments& InArgs)
|
void Construct(const FArguments& InArgs)
|
||||||
{
|
{
|
||||||
InputValue = InArgs._MinNumber;
|
|
||||||
ChildSlot
|
ChildSlot
|
||||||
[
|
[
|
||||||
SNew(SHorizontalBox)
|
SNew(SHorizontalBox)
|
||||||
@ -54,10 +53,11 @@ public:
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
NumericType GetNumber() const { return InputValue; }
|
NumericType GetNumber() const { return InputValue; }
|
||||||
void SetNumber(NumericType InValue) { InputValue = InValue; }
|
|
||||||
private:
|
private:
|
||||||
void OnValueChanged(NumericType NewValue)
|
void OnValueChanged(NumericType NewValue)
|
||||||
{
|
{
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("OnValueChanged newvalue = %d"), (int32)NewValue);
|
||||||
InputValue = NewValue;
|
InputValue = NewValue;
|
||||||
Invalidate(EInvalidateWidget::Layout);
|
Invalidate(EInvalidateWidget::Layout);
|
||||||
}
|
}
|
||||||
|
@ -13,18 +13,17 @@ public:
|
|||||||
: _Title()
|
: _Title()
|
||||||
, _HintText()
|
, _HintText()
|
||||||
, _DefaultText()
|
, _DefaultText()
|
||||||
,_KeyboardType(EKeyboardType::Keyboard_Default)
|
|
||||||
{}
|
{}
|
||||||
SLATE_ARGUMENT(FText, Title) // 标题参数
|
SLATE_ARGUMENT(FText, Title) // 标题参数
|
||||||
SLATE_ARGUMENT(FText, HintText) // 默认值参数
|
SLATE_ARGUMENT(FText, HintText) // 默认值参数
|
||||||
SLATE_ARGUMENT(FText, DefaultText) // 默认值参数
|
SLATE_ARGUMENT(FText, DefaultText) // 默认值参数
|
||||||
SLATE_ARGUMENT(EKeyboardType, KeyboardType)
|
|
||||||
SLATE_END_ARGS()
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
void Construct(const FArguments& InArgs);
|
void Construct(const FArguments& InArgs);
|
||||||
|
|
||||||
FString GetInputText() const { return InputText; }
|
FString GetInputText() const { return InputText; }
|
||||||
void SetInputText(const FString& NewText) { InputText = NewText; }
|
|
||||||
private:
|
private:
|
||||||
FString InputText;
|
FString InputText;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user