21#include <emscripten.h>
22#include <emscripten/bind.h>
24#include "absl/status/status.h"
25#include "absl/strings/str_split.h"
26#include "absl/strings/str_join.h"
43 std::unique_ptr<yaze::cli::BrowserAIService> ai_service;
44 std::string last_output;
46 bool initialized =
false;
56 void SetupAIService() {
57 if (!api_key.empty() && !ai_service) {
58 yaze::cli::BrowserAIConfig config;
59 config.api_key = api_key;
60 config.model =
"gemini-2.0-flash-exp";
61 config.verbose =
false;
62 auto http_client = std::make_unique<yaze::net::EmscriptenHttpClient>();
63 ai_service = std::make_unique<yaze::cli::BrowserAIService>(
64 config, std::move(http_client));
71 if (manager)
return manager->GetCurrentRom();
76static BridgeState g_bridge;
84 return g_bridge.ai_service.get();
88 return g_bridge.GetActiveRom();
97EM_JS(
void, z3ed_print_to_terminal, (
const char* text), {
98 if (window.z3edTerminal) {
99 window.z3edTerminal.print(UTF8ToString(text));
101 console.log(UTF8ToString(text));
106EM_JS(
void, z3ed_error_to_terminal, (
const char* text), {
107 if (window.z3edTerminal) {
108 window.z3edTerminal.printError(UTF8ToString(text));
110 console.error(UTF8ToString(text));
115std::vector<std::string> ParseCommand(
const std::string& command) {
117 return absl::StrSplit(command,
' ', absl::SkipEmpty());
121std::string ProcessCommandInternal(
const std::string& command_str) {
122 auto args = ParseCommand(command_str);
124 return "No command provided";
127 std::ostringstream output;
130 if (args[0] ==
"help") {
131 if (args.size() > 1) {
133 return registry.GenerateCategoryHelp(args[1]);
140 if (args[0] ==
"rom" && args.size() > 1) {
141 if (args[1] ==
"load" && args.size() > 2) {
143 yaze::app::wasm::TriggerRomLoad(args[2]);
144 return "Requesting load for: " + args[2] +
". Check application log.";
146 yaze::Rom* rom = g_bridge.GetActiveRom();
147 if (args[1] ==
"info") {
149 output <<
"ROM Info:\n";
150 output <<
" Size: " << rom->
size() <<
" bytes\n";
151 output <<
" Title: " << rom->
title() <<
"\n";
154 return "No ROM loaded.";
160 if (args[0] ==
"ai" && g_bridge.ai_service) {
161 if (args.size() < 2) {
162 return "AI command requires a prompt. Usage: ai <prompt>";
166 std::vector<std::string> prompt_parts(args.begin() + 1, args.end());
167 std::string prompt = absl::StrJoin(prompt_parts,
" ");
170 auto response = g_bridge.ai_service->GenerateResponse(prompt);
172 return response->text_response;
174 return "AI error: " + std::string(response.status().message());
179 if (args[0] ==
"editor") {
181 if (!editor_manager)
return "Error: Editor manager not available";
183 if (args.size() > 2 && args[1] ==
"switch") {
184 std::string target = args[2];
187 std::transform(
name.begin(),
name.end(),
name.begin(), ::tolower);
188 std::transform(target.begin(), target.end(), target.begin(), ::tolower);
189 if (name == target) {
194 return "Unknown editor: " + args[2];
197 if (args.size() > 3 && args[1] ==
"card") {
201 std::string card_key = args[2];
202 std::string state = args[3];
203 bool visible = (state ==
"show" || state ==
"on" || state ==
"true");
205 auto* current_editor = editor_manager->GetCurrentEditor();
209 if (card_key ==
"object" || card_key ==
"objects" ||
210 card_key ==
"room" || card_key ==
"selector" ||
211 card_key ==
"graphics" || card_key ==
"debug") {
212 return "Panel visibility is now controlled via Layout Designer. Use 'editor layout' commands.";
214 return "Unknown card key for Dungeon Editor: " + card_key;
216 return "Panel control not supported for current editor";
219 if (args.size() > 2 && args[1] ==
"debug" && args[2] ==
"toggle") {
221 return "Debug controls are now accessed via the Layout Designer. "
222 "Use the canvas debug panel or layout commands.";
227 if (args[0] ==
"dungeon") {
229 if (!editor_manager)
return "Error: Editor manager not available";
231 auto* current_editor = editor_manager->GetCurrentEditor();
234 return "Error: Dungeon editor is not active. Use 'editor switch dungeon' first.";
238 if (args.size() > 2 && args[1] ==
"room") {
240 int room_id = std::stoi(args[2],
nullptr, 16);
241 dungeon_editor->FocusRoom(room_id);
242 return "Focused room " + args[2];
243 }
catch (...) {
return "Invalid room ID (hex required)"; }
246 if (args.size() > 2 && args[1] ==
"select_object") {
248 int obj_id = std::stoi(args[2],
nullptr, 16);
249 dungeon_editor->SelectObject(obj_id);
250 return "Selected object " + args[2];
251 }
catch (...) {
return "Invalid object ID (hex required)"; }
254 if (args.size() > 2 && args[1] ==
"agent_mode") {
255 bool enabled = (args[2] ==
"on" || args[2] ==
"true");
256 dungeon_editor->SetAgentMode(enabled);
257 return "Agent mode " + std::string(enabled ?
"enabled" :
"disabled");
263 if (registry.HasCommand(args[0])) {
264 std::vector<std::string> cmd_args(args.begin() + 1, args.end());
266 std::string cmd_output;
267 auto status = registry.Execute(args[0], cmd_args, g_bridge.GetActiveRom(), &cmd_output);
269 return cmd_output.empty() ?
"Command executed successfully" : cmd_output;
271 return "Command failed: " + std::string(status.message());
275 return "Unknown command: " + args[0] +
"\nType 'help' for available commands";
279std::vector<std::string> GetCompletionsInternal(
const std::string& partial) {
280 std::vector<std::string> completions;
283 auto parts = absl::StrSplit(partial,
' ', absl::SkipEmpty());
284 std::vector<std::string> cmd_parts(parts.begin(), parts.end());
287 if (cmd_parts.empty() || (cmd_parts.size() == 1 && !partial.empty() && partial.back() !=
' ')) {
288 std::string prefix = cmd_parts.empty() ?
"" : cmd_parts[0];
292 auto categories = registry.GetCategories();
294 std::set<std::string> unique_commands;
296 for (
const auto& category : categories) {
297 auto commands = registry.GetCommandsInCategory(category);
298 for (
const auto& cmd : commands) {
299 if (prefix.empty() || cmd.find(prefix) == 0) {
300 unique_commands.insert(cmd);
306 std::vector<std::string> special = {
307 "help",
"rom",
"ai",
"clear",
"version",
"hex",
"palette",
"sprite",
308 "music",
"dialogue",
"message",
"resource",
"dungeon",
"overworld",
309 "gui",
"emulator",
"query",
"analyze",
"catalog"
312 for (
const auto& cmd : special) {
313 if (prefix.empty() || cmd.find(prefix) == 0) {
314 unique_commands.insert(cmd);
319 completions.assign(unique_commands.begin(), unique_commands.end());
321 }
else if (cmd_parts.size() >= 1) {
323 const std::string& command = cmd_parts[0];
325 if (command ==
"rom") {
327 std::vector<std::string> rom_cmds = {
"load",
"info",
"save",
"stats",
"verify"};
328 std::string prefix = cmd_parts.size() > 1 ? cmd_parts[1] :
"";
329 for (
const auto& subcmd : rom_cmds) {
330 if (prefix.empty() || subcmd.find(prefix) == 0) {
331 completions.push_back(
"rom " + subcmd);
339 std::sort(completions.begin(), completions.end());
355 g_bridge.Initialize();
358 g_bridge.last_output =
"Invalid command";
359 return g_bridge.last_output.c_str();
362 std::string command_str(command);
363 g_bridge.last_output = ProcessCommandInternal(command_str);
366 z3ed_print_to_terminal(g_bridge.last_output.c_str());
368 return g_bridge.last_output.c_str();
377const char* Z3edGetCompletions(
const char* partial) {
378 g_bridge.Initialize();
381 g_bridge.last_output =
"[]";
382 return g_bridge.last_output.c_str();
385 auto completions = GetCompletionsInternal(std::string(partial));
388 std::ostringstream
json;
390 for (
size_t i = 0; i < completions.size(); ++i) {
391 if (i > 0)
json <<
",";
392 json <<
"\"" << completions[i] <<
"\"";
396 g_bridge.last_output =
json.str();
397 return g_bridge.last_output.c_str();
405void Z3edSetApiKey(
const char* api_key) {
407 g_bridge.api_key = std::string(api_key);
408 g_bridge.SetupAIService();
418 if (!g_bridge.initialized) {
419 g_bridge.Initialize();
421 return g_bridge.initialized ? 1 : 0;
431int Z3edLoadRomData(
const uint8_t* data,
size_t size) {
432 if (!data || size == 0) {
433 z3ed_error_to_terminal(
"Invalid ROM data");
438 std::string temp_path =
"/roms/terminal_upload.sfc";
439 std::ofstream file(temp_path, std::ios::binary);
441 z3ed_error_to_terminal(
"Failed to write to VFS");
444 file.write(
reinterpret_cast<const char*
>(data), size);
448 yaze::app::wasm::TriggerRomLoad(temp_path);
450 z3ed_print_to_terminal(
"ROM uploaded to VFS. Loading...");
459const char* Z3edGetRomInfo() {
460 yaze::Rom* rom = g_bridge.GetActiveRom();
463 g_bridge.last_output =
"{\"error\": \"No ROM loaded\"}";
464 return g_bridge.last_output.c_str();
467 std::ostringstream
json;
469 <<
"\"loaded\": true,"
470 <<
"\"size\": " << rom->
size() <<
","
471 <<
"\"title\": \"" << rom->
title() <<
"\""
474 g_bridge.last_output =
json.str();
475 return g_bridge.last_output.c_str();
484const char* Z3edQueryResource(
const char* query) {
485 g_bridge.Initialize();
488 g_bridge.last_output =
"{\"error\": \"Invalid query\"}";
489 return g_bridge.last_output.c_str();
492 yaze::Rom* rom = g_bridge.GetActiveRom();
495 g_bridge.last_output =
"{\"error\": \"No ROM loaded\"}";
496 return g_bridge.last_output.c_str();
501 std::string cmd_name =
"resource-search";
504 if (registry.HasCommand(cmd_name)) {
506 std::vector<std::string> cmd_args = {
"--query", query,
"--format",
"json"};
508 std::string cmd_output;
509 auto status = registry.Execute(cmd_name, cmd_args, rom, &cmd_output);
512 if (!cmd_output.empty()) {
514 g_bridge.last_output = cmd_output;
515 return g_bridge.last_output.c_str();
517 return "{\"status\":\"success\", \"message\":\"Query executed but no output returned.\"}";
521 g_bridge.last_output =
"{\"error\": \"Resource query failed\"}";
522 return g_bridge.last_output.c_str();
530 emscripten::allow_raw_pointers());
531 emscripten::function(
"getCompletions", &Z3edGetCompletions,
532 emscripten::allow_raw_pointers());
533 emscripten::function(
"setApiKey", &Z3edSetApiKey,
534 emscripten::allow_raw_pointers());
535 emscripten::function(
"isReady", &Z3edIsReady);
536 emscripten::function(
"getRomInfo", &Z3edGetRomInfo,
537 emscripten::allow_raw_pointers());
538 emscripten::function(
"queryResource", &Z3edQueryResource,
539 emscripten::allow_raw_pointers());
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
static CommandRegistry & Instance()
std::string GenerateCompleteHelp() const
Generate complete help text (all commands)
DungeonEditorV2 - Simplified dungeon editor using component delegation.
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
yaze::editor::EditorManager * GetGlobalEditorManager()
BrowserAIService * GetGlobalBrowserAIService()
constexpr std::array< const char *, 14 > kEditorNames
const char * Z3edProcessCommand(const char *command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)