mirror of
https://github.com/shinyflvre/Mate-Engine.git
synced 2026-08-31 18:14:50 +00:00
**Changelog 1.4.0 – THE LOCAL AI Update** **Disclaimer:** The AI runs *entirely locally* and is designed to be environmentally and ethically responsible. - It does **not** harm nature or exploit any artists. - It has only **1.5 billion parameters**, making it a **very small model** by modern standards. - It consumes as much GPU energy as watching a **1440p 60fps YouTube video**. - It was **not trained on artist data**; it uses personal input and public Wikipedia datasets. - It has its own personality, and I ensured it adheres to **ethical standards**. --- ### New Features - Added **Qwen 2.5 1.5B LLM** to MateEngine! - Press **Middle Mouse Button** while hovering over the pet to open the chat window. - Runs fully locally on your GPU — no internet connection required. - Only uses GPU while processing prompts. - Note: This is a **lightweight model** and won’t handle complex tasks or coding. It’s just a small, fun feature! - Added **Resize Button** – Quickly adjust app size for 1080p, 1440p, and 4K monitors. - Added **VRM Information Panel** – Displays polygon count, VRM version, and bone status: - **Perfect** – Properly exported model. - **Failure** – Model has export issues; animations may break. - Added **Options Submenu**. - Added **Volume Sliders** – Independently control pet, effects, and menu volume. - Added **Graphics Settings**. - Added **Dance Trails** – Glowing effects on the pet’s hands during dance. Toggle on/off as desired. - Improved **Music Source Selector** – You can now whitelist or add custom audio sources for dancing. - Added **MateEngine Version Info** – View the current app version from the menu. - Menu can now be opened with **M** key or **Right-Click**. - Added full support for **VRM 1.x** and **VRM 0.x** models. - Introduced **VRM Validator Tool** in Unity Editor – Great for content creators. --- ### Improvements & Fixes - Major **Performance Optimizations**. - Fixed a **GC Error** that could appear after 6–12 hours of continuous use. - Improved **Hand Tracking** – Reduced stuttering in some animations. - Added **Discord Rich Presence** integration. - Added **Glowing Halo** for Steam users as exclusive DLC!
323 lines
11 KiB
C#
323 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System.IO;
|
|
using System.Diagnostics;
|
|
using Debug = UnityEngine.Debug;
|
|
using UnityEngine.UI;
|
|
using LLMUnity;
|
|
using System.Threading.Tasks;
|
|
using System.Linq;
|
|
using System;
|
|
|
|
namespace LLMUnitySamples
|
|
{
|
|
public class KnowledgeBaseGame : KnowledgeBaseGameUI
|
|
{
|
|
[Header("Models")]
|
|
public LLMCharacter llmCharacter;
|
|
public RAG rag;
|
|
public int numRAGResults = 3;
|
|
|
|
string ragPath = "KnowledgeBaseGame.zip";
|
|
Dictionary<string, Dictionary<string, string>> botQuestionAnswers = new Dictionary<string, Dictionary<string, string>>();
|
|
Dictionary<string, RawImage> botImages = new Dictionary<string, RawImage>();
|
|
string currentBotName;
|
|
|
|
new async void Start()
|
|
{
|
|
base.Start();
|
|
CheckLLMs(false);
|
|
InitElements();
|
|
await InitRAG();
|
|
InitLLM();
|
|
}
|
|
|
|
void InitElements()
|
|
{
|
|
PlayerText.interactable = false;
|
|
botImages["Butler"] = ButlerImage;
|
|
botImages["Maid"] = MaidImage;
|
|
botImages["Chef"] = ChefImage;
|
|
botQuestionAnswers["Butler"] = LoadQuestionAnswers(ButlerText.text);
|
|
botQuestionAnswers["Maid"] = LoadQuestionAnswers(MaidText.text);
|
|
botQuestionAnswers["Chef"] = LoadQuestionAnswers(ChefText.text);
|
|
}
|
|
|
|
async Task InitRAG()
|
|
{
|
|
// create the embeddings
|
|
await CreateEmbeddings();
|
|
DropdownChange(CharacterSelect.value);
|
|
}
|
|
|
|
void InitLLM()
|
|
{
|
|
// warm-up the LLM
|
|
PlayerText.text += "Warming up the model...";
|
|
_ = llmCharacter.Warmup(AIReplyComplete);
|
|
}
|
|
|
|
public Dictionary<string, string> LoadQuestionAnswers(string questionAnswersText)
|
|
{
|
|
Dictionary<string, string> questionAnswers = new Dictionary<string, string>();
|
|
foreach (string line in questionAnswersText.Split("\n"))
|
|
{
|
|
if (line == "") continue;
|
|
string[] lineParts = line.Split("|");
|
|
questionAnswers[lineParts[0]] = lineParts[1];
|
|
}
|
|
return questionAnswers;
|
|
}
|
|
|
|
public async Task CreateEmbeddings()
|
|
{
|
|
bool loaded = await rag.Load(ragPath);
|
|
if (!loaded)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Stopwatch stopwatch = new Stopwatch();
|
|
// build the embeddings
|
|
foreach ((string botName, Dictionary<string, string> botQuestionAnswers) in botQuestionAnswers)
|
|
{
|
|
PlayerText.text += $"Creating Embeddings for {botName} (only once)...\n";
|
|
List<string> questions = botQuestionAnswers.Keys.ToList();
|
|
stopwatch.Start();
|
|
foreach (string question in questions) await rag.Add(question, botName);
|
|
stopwatch.Stop();
|
|
Debug.Log($"embedded {rag.Count()} phrases in {stopwatch.Elapsed.TotalMilliseconds / 1000f} secs");
|
|
}
|
|
// store the embeddings
|
|
rag.Save(ragPath);
|
|
#else
|
|
// if in play mode throw an error
|
|
throw new System.Exception("The embeddings could not be found!");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public async Task<List<string>> Retrieval(string question)
|
|
{
|
|
// find similar questions for the current bot using the RAG
|
|
(string[] similarQuestions, _) = await rag.Search(question, numRAGResults, currentBotName);
|
|
// get the answers of the similar questions
|
|
List<string> similarAnswers = new List<string>();
|
|
foreach (string similarQuestion in similarQuestions) similarAnswers.Add(botQuestionAnswers[currentBotName][similarQuestion]);
|
|
return similarAnswers;
|
|
}
|
|
|
|
public async Task<string> ConstructPrompt(string question)
|
|
{
|
|
// get similar answers from the RAG
|
|
List<string> similarAnswers = await Retrieval(question);
|
|
// create the prompt using the user question and the similar answers
|
|
string answers = "";
|
|
foreach (string similarAnswer in similarAnswers) answers += $"\n- {similarAnswer}";
|
|
// string prompt = $"Robot: {currentBotName}\n\n";
|
|
string prompt = $"Question: {question}\n\n";
|
|
prompt += $"Possible Answers: {answers}";
|
|
return prompt;
|
|
}
|
|
|
|
protected async override void OnInputFieldSubmit(string question)
|
|
{
|
|
PlayerText.interactable = false;
|
|
SetAIText("...");
|
|
string prompt = await ConstructPrompt(question);
|
|
_ = llmCharacter.Chat(prompt, SetAIText, AIReplyComplete);
|
|
}
|
|
|
|
protected override void DropdownChange(int selection)
|
|
{
|
|
// select another character
|
|
if (!String.IsNullOrEmpty(currentBotName)) botImages[currentBotName].gameObject.SetActive(false);
|
|
currentBotName = CharacterSelect.options[selection].text;
|
|
botImages[currentBotName].gameObject.SetActive(true);
|
|
Debug.Log($"{currentBotName}: {rag.Count(currentBotName)} phrases available");
|
|
|
|
// set the LLMCharacter name
|
|
llmCharacter.AIName = currentBotName;
|
|
}
|
|
|
|
void SetAIText(string text)
|
|
{
|
|
AIText.text = text;
|
|
}
|
|
|
|
void AIReplyComplete()
|
|
{
|
|
PlayerText.interactable = true;
|
|
PlayerText.Select();
|
|
PlayerText.text = "";
|
|
}
|
|
|
|
public void CancelRequests()
|
|
{
|
|
llmCharacter.CancelRequests();
|
|
AIReplyComplete();
|
|
}
|
|
|
|
public void ExitGame()
|
|
{
|
|
Debug.Log("Exit button clicked");
|
|
Application.Quit();
|
|
}
|
|
|
|
void CheckLLM(LLMCaller llmCaller, bool debug)
|
|
{
|
|
if (!llmCaller.remote && llmCaller.llm != null && llmCaller.llm.model == "")
|
|
{
|
|
string error = $"Please select a llm model in the {llmCaller.llm.gameObject.name} GameObject!";
|
|
if (debug) Debug.LogWarning(error);
|
|
else throw new System.Exception(error);
|
|
}
|
|
}
|
|
|
|
void CheckLLMs(bool debug)
|
|
{
|
|
CheckLLM(rag.search.llmEmbedder, debug);
|
|
CheckLLM(llmCharacter, debug);
|
|
}
|
|
|
|
bool onValidateWarning = true;
|
|
void OnValidate()
|
|
{
|
|
if (onValidateWarning)
|
|
{
|
|
CheckLLMs(true);
|
|
onValidateWarning = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class KnowledgeBaseGameUI : MonoBehaviour
|
|
{
|
|
[Header("UI elements")]
|
|
public Dropdown CharacterSelect;
|
|
public InputField PlayerText;
|
|
public Text AIText;
|
|
|
|
[Header("Bot texts")]
|
|
public TextAsset ButlerText;
|
|
public TextAsset MaidText;
|
|
public TextAsset ChefText;
|
|
|
|
[Header("Bot images")]
|
|
public RawImage ButlerImage;
|
|
public RawImage MaidImage;
|
|
public RawImage ChefImage;
|
|
|
|
[Header("Buttons")]
|
|
public Button NotesButton;
|
|
public Button MapButton;
|
|
public Button SolveButton;
|
|
public Button HelpButton;
|
|
public Button SubmitButton;
|
|
|
|
[Header("Panels")]
|
|
public RawImage NotebookImage;
|
|
public GameObject NotesPanel;
|
|
public GameObject SolvePanel;
|
|
public GameObject HelpPanel;
|
|
public RawImage MapImage;
|
|
public RawImage SuccessImage;
|
|
public Text FailText;
|
|
public Dropdown Answer1;
|
|
public Dropdown Answer2;
|
|
public Dropdown Answer3;
|
|
|
|
protected void Start()
|
|
{
|
|
AddListeners();
|
|
}
|
|
|
|
void OnValueChanged(string newText)
|
|
{
|
|
// Get rid of newline character added when we press enter
|
|
if (Input.GetKey(KeyCode.Return))
|
|
{
|
|
if (PlayerText.text.Trim() == "")
|
|
PlayerText.text = "";
|
|
}
|
|
}
|
|
|
|
protected virtual void AddListeners()
|
|
{
|
|
CharacterSelect.onValueChanged.AddListener(DropdownChange);
|
|
NotesButton.onClick.AddListener(ShowNotes);
|
|
MapButton.onClick.AddListener(ShowMap);
|
|
SolveButton.onClick.AddListener(ShowSolve);
|
|
HelpButton.onClick.AddListener(ShowHelp);
|
|
SubmitButton.onClick.AddListener(SubmitAnswer);
|
|
Answer1.onValueChanged.AddListener(HideFail);
|
|
Answer2.onValueChanged.AddListener(HideFail);
|
|
Answer3.onValueChanged.AddListener(HideFail);
|
|
PlayerText.onSubmit.AddListener(OnInputFieldSubmit);
|
|
PlayerText.onValueChanged.AddListener(OnValueChanged);
|
|
}
|
|
|
|
protected virtual void DropdownChange(int selection) {}
|
|
protected virtual void OnInputFieldSubmit(string question) {}
|
|
|
|
void ShowNotes()
|
|
{
|
|
NotesPanel.gameObject.SetActive(true);
|
|
HelpPanel.gameObject.SetActive(false);
|
|
SolvePanel.gameObject.SetActive(false);
|
|
NotebookImage.gameObject.SetActive(true);
|
|
}
|
|
|
|
void ShowMap()
|
|
{
|
|
MapImage.gameObject.SetActive(true);
|
|
}
|
|
|
|
void HideFail(int selection)
|
|
{
|
|
FailText.gameObject.SetActive(false);
|
|
}
|
|
|
|
void ShowSolve()
|
|
{
|
|
HideFail(0);
|
|
NotesPanel.gameObject.SetActive(false);
|
|
HelpPanel.gameObject.SetActive(false);
|
|
SolvePanel.gameObject.SetActive(true);
|
|
NotebookImage.gameObject.SetActive(true);
|
|
}
|
|
|
|
void ShowHelp()
|
|
{
|
|
NotesPanel.gameObject.SetActive(false);
|
|
HelpPanel.gameObject.SetActive(true);
|
|
SolvePanel.gameObject.SetActive(false);
|
|
NotebookImage.gameObject.SetActive(true);
|
|
}
|
|
|
|
void SubmitAnswer()
|
|
{
|
|
if (Answer1.options[Answer1.value].text == "Professor Pluot" && Answer2.options[Answer2.value].text == "Living Room" && Answer3.options[Answer3.value].text == "A Hollow Bible")
|
|
{
|
|
NotebookImage.gameObject.SetActive(false);
|
|
SuccessImage.gameObject.SetActive(true);
|
|
}
|
|
else
|
|
{
|
|
FailText.gameObject.SetActive(true);
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
foreach (RawImage image in new RawImage[] {NotebookImage, MapImage, SuccessImage})
|
|
{
|
|
if (image.IsActive() && !RectTransformUtility.RectangleContainsScreenPoint(image.rectTransform, Input.mousePosition))
|
|
{
|
|
image.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|