yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_terminal_bridge.cc
Go to the documentation of this file.
1
10#ifdef __EMSCRIPTEN__
11
12#include <algorithm>
13#include <cstring>
14#include <fstream>
15#include <memory>
16#include <set>
17#include <sstream>
18#include <string>
19#include <vector>
20
21#include <emscripten.h>
22#include <emscripten/bind.h>
23
24#include "absl/status/status.h"
25#include "absl/strings/str_join.h"
26#include "absl/strings/str_split.h"
34#include "rom/rom.h"
35
36namespace yaze::app {
37extern editor::EditorManager* GetGlobalEditorManager();
38}
39
40namespace {
41
42struct BridgeState {
43 std::unique_ptr<yaze::cli::BrowserAIService> ai_service;
44 std::string last_output;
45 std::string provider = yaze::cli::kProviderGemini;
46 std::string model = "gemini-2.5-flash";
47 std::string api_base;
48 std::string api_key;
49 bool initialized = false;
50
51 void Initialize() {
52 if (!initialized) {
53 // Ensure command registry is initialized
55 initialized = true;
56 }
57 }
58
59 void SetupAIService() {
60 yaze::cli::BrowserAIConfig config;
61 config.provider = provider.empty() ? yaze::cli::kProviderGemini : provider;
62 config.api_key = api_key;
63 config.model = model;
64 config.api_base = api_base;
65 config.verbose = false;
66 auto http_client = std::make_unique<yaze::net::EmscriptenHttpClient>();
67 ai_service = std::make_unique<yaze::cli::BrowserAIService>(
68 config, std::move(http_client));
69 if (yaze::Rom* rom = GetActiveRom()) {
70 ai_service->SetRomContext(rom);
71 }
72 }
73
74 void ConfigureAI(const char* provider_value, const char* model_value,
75 const char* api_base_value, const char* api_key_value) {
76 if (provider_value) {
77 provider = provider_value;
78 }
79 if (model_value) {
80 model = model_value;
81 }
82 if (api_base_value) {
83 api_base = api_base_value;
84 }
85 if (api_key_value) {
86 api_key = api_key_value;
87 }
88 SetupAIService();
89 }
90
91 // Helper to get the REAL active ROM from the application controller
92 yaze::Rom* GetActiveRom() {
93 auto* manager = yaze::app::GetGlobalEditorManager();
94 if (manager)
95 return manager->GetCurrentRom();
96 return nullptr;
97 }
98};
99
100static BridgeState g_bridge;
101
102} // namespace
103
104// Global accessor for command handlers
105namespace yaze {
106namespace cli {
107BrowserAIService* GetGlobalBrowserAIService() {
108 return g_bridge.ai_service.get();
109}
110
112 return g_bridge.GetActiveRom();
113}
114
115} // namespace cli
116} // namespace yaze
117
118namespace {
119
120// JavaScript function to print to terminal
121EM_JS(void, z3ed_print_to_terminal, (const char* text), {
122 if (window.z3edTerminal) {
123 window.z3edTerminal.print(UTF8ToString(text));
124 } else {
125 console.log(UTF8ToString(text));
126 }
127});
128
129// JavaScript function to print error to terminal
130EM_JS(void, z3ed_error_to_terminal, (const char* text), {
131 if (window.z3edTerminal) {
132 window.z3edTerminal.printError(UTF8ToString(text));
133 } else {
134 console.error(UTF8ToString(text));
135 }
136});
137
138// Parse command string into arguments
139std::vector<std::string> ParseCommand(const std::string& command) {
140 // Simple tokenization - could be enhanced for quoted strings
141 return absl::StrSplit(command, ' ', absl::SkipEmpty());
142}
143
144// Process command and return output
145std::string ProcessCommandInternal(const std::string& command_str) {
146 auto args = ParseCommand(command_str);
147 if (args.empty()) {
148 return "No command provided";
149 }
150
151 std::ostringstream output;
152
153 // Handle special commands
154 if (args[0] == "help") {
155 if (args.size() > 1) {
156 auto& registry = yaze::cli::CommandRegistry::Instance();
157 return registry.GenerateCategoryHelp(args[1]);
158 } else {
160 }
161 }
162
163 // Handle ROM commands
164 if (args[0] == "rom" && args.size() > 1) {
165 if (args[1] == "load" && args.size() > 2) {
166 // Trigger full application load via bootstrap
167 yaze::app::wasm::TriggerRomLoad(args[2]);
168 return "Requesting load for: " + args[2] + ". Check application log.";
169 }
170 yaze::Rom* rom = g_bridge.GetActiveRom();
171 if (args[1] == "info") {
172 if (rom && rom->is_loaded()) {
173 output << "ROM Info:\n";
174 output << " Size: " << rom->size() << " bytes\n";
175 output << " Title: " << rom->title() << "\n";
176 return output.str();
177 } else {
178 return "No ROM loaded.";
179 }
180 }
181 }
182
183 // Handle AI commands if service is available
184 if (args[0] == "ai") {
185 if (!g_bridge.ai_service) {
186 g_bridge.SetupAIService();
187 }
188 if (!g_bridge.ai_service) {
189 return "AI service unavailable";
190 }
191 if (args.size() < 2) {
192 return "AI command requires a prompt. Usage: ai <prompt>";
193 }
194
195 // Reconstruct prompt from remaining args
196 std::vector<std::string> prompt_parts(args.begin() + 1, args.end());
197 std::string prompt = absl::StrJoin(prompt_parts, " ");
198
199 // Generate response using AI service
200 auto response = g_bridge.ai_service->GenerateResponse(prompt);
201 if (response.ok()) {
202 return response->text_response;
203 } else {
204 return "AI error: " + std::string(response.status().message());
205 }
206 }
207
208 // Handle editor commands
209 if (args[0] == "editor") {
210 auto* editor_manager = yaze::app::GetGlobalEditorManager();
211 if (!editor_manager)
212 return "Error: Editor manager not available";
213
214 if (args.size() > 2 && args[1] == "switch") {
215 std::string target = args[2];
216 for (size_t i = 0; i < yaze::editor::kEditorNames.size(); ++i) {
217 std::string name = yaze::editor::kEditorNames[i];
218 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
219 std::transform(target.begin(), target.end(), target.begin(), ::tolower);
220 if (name == target) {
221 editor_manager->SwitchToEditor(
222 static_cast<yaze::editor::EditorType>(i));
223 return "Switched to " + std::string(yaze::editor::kEditorNames[i]);
224 }
225 }
226 return "Unknown editor: " + args[2];
227 }
228
229 if (args.size() > 3 && args[1] == "card") {
230 // "editor card <name> <show/hide>"
231 // Name might be multi-word if parsed poorly, but let's assume simple args or quotes handled by caller (currently not)
232 // For simple support: "editor card object show"
233 std::string card_key = args[2];
234 std::string state = args[3];
235 bool visible = (state == "show" || state == "on" || state == "true");
236
237 auto* current_editor = editor_manager->GetCurrentEditor();
238 if (current_editor &&
240 // Panel visibility is now controlled via the Layout Designer
241 // These legacy panel toggles are no longer directly accessible
242 if (card_key == "object" || card_key == "objects" ||
243 card_key == "room" || card_key == "selector" ||
244 card_key == "graphics" || card_key == "debug") {
245 return "Panel visibility is now controlled via Layout Designer. Use "
246 "'editor layout' commands.";
247 }
248 return "Unknown card key for Dungeon Editor: " + card_key;
249 }
250 return "Panel control not supported for current editor";
251 }
252
253 if (args.size() > 2 && args[1] == "debug" && args[2] == "toggle") {
254 // Legacy command - debug controls are now accessed via Layout Designer
255 return "Debug controls are now accessed via the Layout Designer. "
256 "Use the canvas debug panel or layout commands.";
257 }
258 }
259
260 // Handle dungeon commands
261 if (args[0] == "dungeon") {
262 auto* editor_manager = yaze::app::GetGlobalEditorManager();
263 if (!editor_manager)
264 return "Error: Editor manager not available";
265
266 auto* current_editor = editor_manager->GetCurrentEditor();
267 if (!current_editor ||
269 // Auto-switch if possible? Or just fail.
270 return "Error: Dungeon editor is not active. Use 'editor switch dungeon' "
271 "first.";
272 }
273 auto* dungeon_editor =
275
276 if (args.size() > 2 && args[1] == "room") {
277 try {
278 int room_id = std::stoi(args[2], nullptr, 16);
279 dungeon_editor->FocusRoom(room_id);
280 return "Focused room " + args[2];
281 } catch (...) {
282 return "Invalid room ID (hex required)";
283 }
284 }
285
286 if (args.size() > 2 && args[1] == "select_object") {
287 try {
288 int obj_id = std::stoi(args[2], nullptr, 16);
289 dungeon_editor->SelectObject(obj_id);
290 return "Selected object " + args[2];
291 } catch (...) {
292 return "Invalid object ID (hex required)";
293 }
294 }
295
296 if (args.size() > 2 && args[1] == "agent_mode") {
297 bool enabled = (args[2] == "on" || args[2] == "true");
298 dungeon_editor->SetAgentMode(enabled);
299 return "Agent mode " + std::string(enabled ? "enabled" : "disabled");
300 }
301 }
302
303 // Try command registry
304 auto& registry = yaze::cli::CommandRegistry::Instance();
305 if (registry.HasCommand(args[0])) {
306 std::vector<std::string> cmd_args(args.begin() + 1, args.end());
307 // Use the REAL active ROM
308 std::string cmd_output;
309 auto status = registry.Execute(args[0], cmd_args, g_bridge.GetActiveRom(),
310 &cmd_output);
311 if (status.ok()) {
312 return cmd_output.empty() ? "Command executed successfully" : cmd_output;
313 } else {
314 return "Command failed: " + std::string(status.message());
315 }
316 }
317
318 return "Unknown command: " + args[0] + "\nType 'help' for available commands";
319}
320
321// Get command completions for autocomplete
322std::vector<std::string> GetCompletionsInternal(const std::string& partial) {
323 std::vector<std::string> completions;
324
325 // Parse the partial command to understand context
326 auto parts = absl::StrSplit(partial, ' ', absl::SkipEmpty());
327 std::vector<std::string> cmd_parts(parts.begin(), parts.end());
328
329 // If empty or single word, show top-level commands
330 if (cmd_parts.empty() ||
331 (cmd_parts.size() == 1 && !partial.empty() && partial.back() != ' ')) {
332 std::string prefix = cmd_parts.empty() ? "" : cmd_parts[0];
333
334 // Get all available commands from registry
335 auto& registry = yaze::cli::CommandRegistry::Instance();
336 auto categories = registry.GetCategories();
337
338 std::set<std::string> unique_commands; // Use set to avoid duplicates
339
340 for (const auto& category : categories) {
341 auto commands = registry.GetCommandsInCategory(category);
342 for (const auto& cmd : commands) {
343 if (prefix.empty() || cmd.find(prefix) == 0) {
344 unique_commands.insert(cmd);
345 }
346 }
347 }
348
349 // Add special/built-in commands
350 std::vector<std::string> special = {
351 "help", "rom", "ai", "clear", "version",
352 "hex", "palette", "sprite", "music", "dialogue",
353 "message", "resource", "dungeon", "overworld", "gui",
354 "emulator", "query", "analyze", "catalog"};
355
356 for (const auto& cmd : special) {
357 if (prefix.empty() || cmd.find(prefix) == 0) {
358 unique_commands.insert(cmd);
359 }
360 }
361
362 // Convert set to vector
363 completions.assign(unique_commands.begin(), unique_commands.end());
364
365 } else if (cmd_parts.size() >= 1) {
366 // Context-specific completions based on the command
367 const std::string& command = cmd_parts[0];
368
369 if (command == "rom") {
370 // ROM subcommands
371 std::vector<std::string> rom_cmds = {"load", "info", "save", "stats",
372 "verify"};
373 std::string prefix = cmd_parts.size() > 1 ? cmd_parts[1] : "";
374 for (const auto& subcmd : rom_cmds) {
375 if (prefix.empty() || subcmd.find(prefix) == 0) {
376 completions.push_back("rom " + subcmd);
377 }
378 }
379 }
380 // ... (other completions logic can be kept or expanded via registry in future)
381 }
382
383 // Sort completions alphabetically
384 std::sort(completions.begin(), completions.end());
385
386 return completions;
387}
388
389} // namespace
390
391extern "C" {
392
398EMSCRIPTEN_KEEPALIVE
399const char* Z3edProcessCommand(const char* command) {
400 g_bridge.Initialize();
401
402 if (!command) {
403 g_bridge.last_output = "Invalid command";
404 return g_bridge.last_output.c_str();
405 }
406
407 std::string command_str(command);
408 g_bridge.last_output = ProcessCommandInternal(command_str);
409
410 // Also print to terminal for visual feedback
411 z3ed_print_to_terminal(g_bridge.last_output.c_str());
412
413 return g_bridge.last_output.c_str();
414}
415
421EMSCRIPTEN_KEEPALIVE
422const char* Z3edGetCompletions(const char* partial) {
423 g_bridge.Initialize();
424
425 if (!partial) {
426 g_bridge.last_output = "[]";
427 return g_bridge.last_output.c_str();
428 }
429
430 auto completions = GetCompletionsInternal(std::string(partial));
431
432 // Convert to JSON array
433 std::ostringstream json;
434 json << "[";
435 for (size_t i = 0; i < completions.size(); ++i) {
436 if (i > 0)
437 json << ",";
438 json << "\"" << completions[i] << "\"";
439 }
440 json << "]";
441
442 g_bridge.last_output = json.str();
443 return g_bridge.last_output.c_str();
444}
445
450EMSCRIPTEN_KEEPALIVE
451void Z3edSetApiKey(const char* api_key) {
452 g_bridge.ConfigureAI(nullptr, nullptr, nullptr, api_key ? api_key : "");
453}
454
462EMSCRIPTEN_KEEPALIVE
463void Z3edConfigureAI(const char* provider, const char* model,
464 const char* api_base, const char* api_key) {
465 g_bridge.Initialize();
466 g_bridge.ConfigureAI(provider ? provider : yaze::cli::kProviderGemini,
467 model ? model : "", api_base ? api_base : "",
468 api_key ? api_key : "");
469}
470
475EMSCRIPTEN_KEEPALIVE
476int Z3edIsReady() {
477 if (!g_bridge.initialized) {
478 g_bridge.Initialize();
479 }
480 return g_bridge.initialized ? 1 : 0;
481}
482
489EMSCRIPTEN_KEEPALIVE
490int Z3edLoadRomData(const uint8_t* data, size_t size) {
491 if (!data || size == 0) {
492 z3ed_error_to_terminal("Invalid ROM data");
493 return 0;
494 }
495
496 // Write to a temporary file
497 std::string temp_path = "/.yaze/roms/terminal_upload.sfc";
498 std::ofstream file(temp_path, std::ios::binary);
499 if (!file) {
500 z3ed_error_to_terminal("Failed to write to VFS");
501 return 0;
502 }
503 file.write(reinterpret_cast<const char*>(data), size);
504 file.close();
505
506 // Trigger load via bootstrap (which calls Application::LoadRom)
507 yaze::app::wasm::TriggerRomLoad(temp_path);
508
509 z3ed_print_to_terminal("ROM uploaded to VFS. Loading...");
510 return 1;
511}
512
517EMSCRIPTEN_KEEPALIVE
518const char* Z3edGetRomInfo() {
519 yaze::Rom* rom = g_bridge.GetActiveRom();
520
521 if (!rom || !rom->is_loaded()) {
522 g_bridge.last_output = "{\"error\": \"No ROM loaded\"}";
523 return g_bridge.last_output.c_str();
524 }
525
526 std::ostringstream json;
527 json << "{"
528 << "\"loaded\": true,"
529 << "\"size\": " << rom->size() << ","
530 << "\"title\": \"" << rom->title() << "\""
531 << "}";
532
533 g_bridge.last_output = json.str();
534 return g_bridge.last_output.c_str();
535}
536
542EMSCRIPTEN_KEEPALIVE
543const char* Z3edQueryResource(const char* query) {
544 g_bridge.Initialize();
545
546 if (!query) {
547 g_bridge.last_output = "{\"error\": \"Invalid query\"}";
548 return g_bridge.last_output.c_str();
549 }
550
551 yaze::Rom* rom = g_bridge.GetActiveRom();
552
553 if (!rom || !rom->is_loaded()) {
554 g_bridge.last_output = "{\"error\": \"No ROM loaded\"}";
555 return g_bridge.last_output.c_str();
556 }
557
558 // Process resource query using command registry
559 // Map to 'resource-search' which is the actual registered command
560 std::string cmd_name = "resource-search";
561 auto& registry = yaze::cli::CommandRegistry::Instance();
562
563 if (registry.HasCommand(cmd_name)) {
564 // Construct args: resource-search --query <query> --format json
565 std::vector<std::string> cmd_args = {"--query", query, "--format", "json"};
566
567 std::string cmd_output;
568 auto status = registry.Execute(cmd_name, cmd_args, rom, &cmd_output);
569 if (status.ok()) {
570 // If output captured, return it directly
571 if (!cmd_output.empty()) {
572 // We might want to update last_output too as a side effect if the bridge uses it
573 g_bridge.last_output = cmd_output;
574 return g_bridge.last_output.c_str();
575 }
576 return "{\"status\":\"success\", \"message\":\"Query executed but no "
577 "output returned.\"}";
578 }
579 }
580
581 g_bridge.last_output = "{\"error\": \"Resource query failed\"}";
582 return g_bridge.last_output.c_str();
583}
584
585} // extern "C"
586
587// Emscripten module initialization
588EMSCRIPTEN_BINDINGS(z3ed_terminal) {
589 emscripten::function("processCommand", &Z3edProcessCommand,
590 emscripten::allow_raw_pointers());
591 emscripten::function("getCompletions", &Z3edGetCompletions,
592 emscripten::allow_raw_pointers());
593 emscripten::function("setApiKey", &Z3edSetApiKey,
594 emscripten::allow_raw_pointers());
595 emscripten::function("configureAI", &Z3edConfigureAI,
596 emscripten::allow_raw_pointers());
597 emscripten::function("isReady", &Z3edIsReady);
598 emscripten::function("getRomInfo", &Z3edGetRomInfo,
599 emscripten::allow_raw_pointers());
600 emscripten::function("queryResource", &Z3edQueryResource,
601 emscripten::allow_raw_pointers());
602}
603
604#endif // __EMSCRIPTEN__
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
static CommandRegistry & Instance()
std::string GenerateCompleteHelp() const
Generate complete help text (all commands)
DungeonEditorV2 - Simplified dungeon editor using component delegation.
EditorType type() const
Definition editor.h:293
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()
Definition main.cc:123
constexpr char kProviderGemini[]
Definition provider_ids.h:9
Rom * GetGlobalRom()
BrowserAIService * GetGlobalBrowserAIService()
Rom * rom()
Get the current ROM instance.
Editor * current_editor()
Get the currently active editor.
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:221
const char * Z3edProcessCommand(const char *command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)