AI Chatbot Editor Plugin
UE5 C++ editor plugin that registers a toolbar command, opens a standalone Slate chat window, sends prompts to the Mistral API, and renders the parsed grid response as interactive Slate buttons - all inside the editor.
Project at a Glance
This repository is a UE5 C++ project (JoinTask) hosting an editor plugin (MinesButton). The plugin registers a command in the Unreal Editor toolbar and opens a standalone Slate window with a chat interface. The interface submits a natural-language prompt to Mistral's chat API, parses the response JSON, and renders the extracted grid as a clickable Slate button grid.
End-to-end flow:
- Editor toolbar/menu command launches a custom Slate window
- User types a prompt and presses Enter
- Plugin sends HTTP POST to Mistral
/v1/chat/completions - Response JSON is parsed (
choices[0].message.content) - Extracted grid text is displayed; Play generates an interactive Slate button grid
Quick Facts
| Field | Value |
|---|---|
| Engine | Unreal Engine 5.3 |
| Language | C++ (Unreal C++ + Slate) |
| Primary Runtime | Editor plugin - toolbar + menu command |
| Secondary Runtime | In-game HUD-backed Slate injection (prepared, not default flow) |
| AI Provider | Mistral Chat Completions API |
| UI Stack | Slate - no UMG in the main chat widget path |
| Build System | Unreal Build Tool (.Build.cs, Target.cs) |
| Project Type | UE C++ project (JoinTask) with custom plugin (MinesButton) |
| Maturity | Prototype / experimental |
Repository Structure
Source/JoinTask/*- base game module and targets (minimal gameplay logic)Plugins/MinesButton/Source/MinesButton/Private/MinesButton.cpp- plugin module lifecycle, command binding, menu/toolbar integration, window spawnSChatWidget.cpp- Slate chat widget, response processing, grid renderingMinesweeperAIRequest.cpp- HTTP request construction and callback wiringMinesButtonStyle.cpp+MinesButtonCommands.cpp- command and icon/style registrationWindowHUD.cpp- alternate HUD path for viewport widget injection
Config/*.ini- UE project + rendering/input/editor/game configJoinTask.uproject+MinesButton.uplugin- module/plugin metadata and load topology
Runtime Architecture
Core Components
FMinesButtonModule - Editor integration + window orchestration
Plugin entrypoint and Editor UI integration.
- Initializes style and command registration in
StartupModule - Binds command execution to
PluginButtonClicked - Extends
LevelEditor.MainMenu.WindowandLevelEditor.LevelEditorToolBar.PlayToolBar - Spawns a dedicated
SWindowcontainingSChatWidget - Maintains widget/window references to prevent duplicates and raise existing window to front
Design note: Module type is Editor in .uplugin - correctly scoped for editor-time workflows only.
SChatWidget - Presentation + interaction + response shaping
User-facing Slate UI and main orchestration node.
UI tree: prompt label → SEditableTextBox → response STextBlock → Play button (disabled until response) → SVerticalBox grid container.
- On Enter: forwards raw prompt to AI request object
- On response: parses JSON, extracts model content field, applies post-processing to strip explanatory text, enables grid action
- On Play: tokenizes multiline response into rows/cells, builds
SHorizontalBoxrows filled with per-cellSButtonwidgets
Note: Grid generation is presentational. No game-state model for reveal/flag/win-loss rules exists yet.
UMinesweeperAIRequest - Network adapter
Thin HTTP call lifecycle wrapper.
- Builds POST request to
https://api.mistral.ai/v1/chat/completions - Sets
Content-Typeand bearer authorization headers - Sends user prompt as JSON body
- Returns raw response body via delegate to
SChatWidget
Acts as a gateway only - semantic parsing is left to SChatWidget.
FMinesButtonCommands + FMinesButtonStyle - Command and style system
- Defines the
PluginActioncommand exposed to menu and toolbar - Registers style set and SVG icon from plugin
Resourcesdirectory
AWindowHUD - Alternate runtime hook
Creates and attaches SChatWidget to GameViewport via SWeakWidget on BeginPlay. Present as a second integration path; the primary feature path is the editor-window flow.
Build & Module Topology
Host project (JoinTask)
- Standard game module (
IMPLEMENT_PRIMARY_GAME_MODULE) - Targets:
JoinTaskTarget(Game) +JoinTaskEditorTarget(Editor)
Plugin module (MinesButton)
- Type:
Editor, loaded in default phase - Dependencies: Core + Slate/SlateCore + ToolMenus/UnrealEd/EditorFramework + HTTP + Json + JsonUtilities
This dependency shape matches the plugin's responsibilities: Editor extensibility, network, JSON, and custom Slate UI.
Code Snippet
void UMinesweeperAIRequest::SendAIRequest(const FString& UserPrompt)
{
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
FHttpModule::Get().CreateRequest();
Request->SetVerb(TEXT("POST"));
Request->SetURL(TEXT("https://api.mistral.ai/v1/chat/completions"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
Request->SetHeader(TEXT("Authorization"),
FString::Printf(TEXT("Bearer %s"), *BearerToken)); // TODO: read from config
// NOTE: Prompt is interpolated directly - minimal escaping applied.
// Production path: use FJsonWriter to build payload safely.
const FString Body = FString::Printf(
TEXT("{\"model\":\"mistral-small\","
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]}"),
*UserPrompt);
Request->SetContentAsString(Body);
Request->OnProcessRequestComplete().BindUObject(
this, &UMinesweeperAIRequest::OnResponseReceived);
Request->ProcessRequest();
}
void UMinesweeperAIRequest::OnResponseReceived(
FHttpRequestPtr Request,
FHttpResponsePtr Response,
bool bSuccess)
{
if (!bSuccess || !Response.IsValid())
{
OnAIResponseReceived.ExecuteIfBound(TEXT("Error: request failed."));
return;
}
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader =
TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
const TArray<TSharedPtr<FJsonValue>>* Choices;
if (JsonObject->TryGetArrayField(TEXT("choices"), Choices) && Choices->Num() > 0)
{
const FString Content = (*Choices)[0]
->AsObject()->GetObjectField(TEXT("message"))
->GetStringField(TEXT("content"));
OnAIResponseReceived.ExecuteIfBound(Content);
return;
}
}
OnAIResponseReceived.ExecuteIfBound(TEXT("Error: could not parse response."));
}Data & Control Flow
- Module startup -
StartupModuleinitializes style and commands;UToolMenus::RegisterStartupCallbackdefers menu extension until menus are ready - User opens plugin -
PluginButtonClickedcreates a freshSWindow + SChatWidgetor raises the existing one - Prompt submission -
OnChatSubmittedfires onETextCommit::OnEnterand forwards text toUMinesweeperAIRequest - HTTP exchange - POST request emitted to Mistral;
OnProcessRequestCompletecallback fires with raw JSON body - Response parsing -
HandleAIResponsedeserializes JSON, extractschoices[0].message.content, strips explanatory phrases, enables Play - Grid rendering -
GenerateMinesweeperBoardtokenizes response by newlines and spaces; each token becomes anSButtonin a visual grid
Video Demo
Strengths
- Clean separation between Editor module/bootstrap, UI widget orchestration, and network transport
- Uses Unreal-native extension points - ToolMenus, command lists, Slate style registry
- Fast prototype path from prompt to rendered UI with minimal moving parts
- Mermaid runtime architecture is easy to follow and extend
Technical Risks
| Risk | Detail |
|---|---|
| Hardcoded bearer token | Token is set directly in C++ source; must move to per-user config or env-driven retrieval |
| Threading assumptions | UI updated directly in callback path; explicit game-thread marshaling (AsyncTask) would harden safety |
| Response contract fragility | Assumes exact JSON schema; no fallback for schema drift or unexpected model formatting |
| Prompt injection surface | User text interpolated into JSON via FString::Printf - minimal escaping applied |
| No timeout / retry | Network errors collapse to a generic failure string |
| No gameplay model | Generated grid is visual only; minesweeper mechanics (reveal, flag, win/loss) not implemented |
Suggested Evolution Path
- Security - Remove hardcoded token; read from
DefaultEditorPerProjectUserSettings.ini(per-user, gitignored) or environment variable - Protocol robustness - Build request payload with
FJsonWriter; add schema validation with graceful error UI - UX - Scrollable chat history, loading indicator, input disabled while request in flight, retry action
- Game logic - Introduce board model (cells, mines, reveal state, adjacency counts) decoupled from rendering
- Integration clarity - Decide between editor-only tool vs runtime HUD feature; remove dead path or formally support both modes
- Testability - Extract JSON parser and grid formatter into pure utility functions for isolated unit tests
Bottom Line
A prototype-grade Unreal Editor plugin demonstrating an end-to-end AI-assisted workflow in Slate: command registration, custom window lifecycle, external API call via HTTP Module, JSON parsing, and dynamic widget rendering. The module → widget → request adapter shape is clean, but the path to production tooling requires hardening around secrets management, error handling, and domain modeling.