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 <memory>
15#include <set>
16#include <sstream>
17#include <string>
18#include <vector>
19#include <fstream>
20
21#include <emscripten.h>
22#include <emscripten/bind.h>
23
24#include "absl/status/status.h"
25#include "absl/strings/str_split.h"
26#include "absl/strings/str_join.h"
27#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 api_key;
46 bool initialized = false;
47
48 void Initialize() {
49 if (!initialized) {
50 // Ensure command registry is initialized
52 initialized = true;
53 }
54 }
55
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));
65 }
66 }
67
68 // Helper to get the REAL active ROM from the application controller
69 yaze::Rom* GetActiveRom() {
70 auto* manager = yaze::app::GetGlobalEditorManager();
71 if (manager) return manager->GetCurrentRom();
72 return nullptr;
73 }
74};
75
76static BridgeState g_bridge;
77
78} // namespace
79
80// Global accessor for command handlers
81namespace yaze {
82namespace cli {
83BrowserAIService* GetGlobalBrowserAIService() {
84 return g_bridge.ai_service.get();
85}
86
87Rom* GetGlobalRom() {
88 return g_bridge.GetActiveRom();
89}
90
91} // namespace cli
92} // namespace yaze
93
94namespace {
95
96// JavaScript function to print to terminal
97EM_JS(void, z3ed_print_to_terminal, (const char* text), {
98 if (window.z3edTerminal) {
99 window.z3edTerminal.print(UTF8ToString(text));
100 } else {
101 console.log(UTF8ToString(text));
102 }
103});
104
105// JavaScript function to print error to terminal
106EM_JS(void, z3ed_error_to_terminal, (const char* text), {
107 if (window.z3edTerminal) {
108 window.z3edTerminal.printError(UTF8ToString(text));
109 } else {
110 console.error(UTF8ToString(text));
111 }
112});
113
114// Parse command string into arguments
115std::vector<std::string> ParseCommand(const std::string& command) {
116 // Simple tokenization - could be enhanced for quoted strings
117 return absl::StrSplit(command, ' ', absl::SkipEmpty());
118}
119
120// Process command and return output
121std::string ProcessCommandInternal(const std::string& command_str) {
122 auto args = ParseCommand(command_str);
123 if (args.empty()) {
124 return "No command provided";
125 }
126
127 std::ostringstream output;
128
129 // Handle special commands
130 if (args[0] == "help") {
131 if (args.size() > 1) {
132 auto& registry = yaze::cli::CommandRegistry::Instance();
133 return registry.GenerateCategoryHelp(args[1]);
134 } else {
136 }
137 }
138
139 // Handle ROM commands
140 if (args[0] == "rom" && args.size() > 1) {
141 if (args[1] == "load" && args.size() > 2) {
142 // Trigger full application load via bootstrap
143 yaze::app::wasm::TriggerRomLoad(args[2]);
144 return "Requesting load for: " + args[2] + ". Check application log.";
145 }
146 yaze::Rom* rom = g_bridge.GetActiveRom();
147 if (args[1] == "info") {
148 if (rom && rom->is_loaded()) {
149 output << "ROM Info:\n";
150 output << " Size: " << rom->size() << " bytes\n";
151 output << " Title: " << rom->title() << "\n";
152 return output.str();
153 } else {
154 return "No ROM loaded.";
155 }
156 }
157 }
158
159 // Handle AI commands if service is available
160 if (args[0] == "ai" && g_bridge.ai_service) {
161 if (args.size() < 2) {
162 return "AI command requires a prompt. Usage: ai <prompt>";
163 }
164
165 // Reconstruct prompt from remaining args
166 std::vector<std::string> prompt_parts(args.begin() + 1, args.end());
167 std::string prompt = absl::StrJoin(prompt_parts, " ");
168
169 // Generate response using AI service
170 auto response = g_bridge.ai_service->GenerateResponse(prompt);
171 if (response.ok()) {
172 return response->text_response;
173 } else {
174 return "AI error: " + std::string(response.status().message());
175 }
176 }
177
178 // Handle editor commands
179 if (args[0] == "editor") {
180 auto* editor_manager = yaze::app::GetGlobalEditorManager();
181 if (!editor_manager) return "Error: Editor manager not available";
182
183 if (args.size() > 2 && args[1] == "switch") {
184 std::string target = args[2];
185 for (size_t i = 0; i < yaze::editor::kEditorNames.size(); ++i) {
186 std::string name = yaze::editor::kEditorNames[i];
187 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
188 std::transform(target.begin(), target.end(), target.begin(), ::tolower);
189 if (name == target) {
190 editor_manager->SwitchToEditor(static_cast<yaze::editor::EditorType>(i));
191 return "Switched to " + std::string(yaze::editor::kEditorNames[i]);
192 }
193 }
194 return "Unknown editor: " + args[2];
195 }
196
197 if (args.size() > 3 && args[1] == "card") {
198 // "editor card <name> <show/hide>"
199 // Name might be multi-word if parsed poorly, but let's assume simple args or quotes handled by caller (currently not)
200 // For simple support: "editor card object show"
201 std::string card_key = args[2];
202 std::string state = args[3];
203 bool visible = (state == "show" || state == "on" || state == "true");
204
205 auto* current_editor = editor_manager->GetCurrentEditor();
206 if (current_editor && current_editor->type() == yaze::editor::EditorType::kDungeon) {
207 // Panel visibility is now controlled via the Layout Designer
208 // These legacy panel toggles are no longer directly accessible
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.";
213 }
214 return "Unknown card key for Dungeon Editor: " + card_key;
215 }
216 return "Panel control not supported for current editor";
217 }
218
219 if (args.size() > 2 && args[1] == "debug" && args[2] == "toggle") {
220 // Legacy command - debug controls are now accessed via Layout Designer
221 return "Debug controls are now accessed via the Layout Designer. "
222 "Use the canvas debug panel or layout commands.";
223 }
224 }
225
226 // Handle dungeon commands
227 if (args[0] == "dungeon") {
228 auto* editor_manager = yaze::app::GetGlobalEditorManager();
229 if (!editor_manager) return "Error: Editor manager not available";
230
231 auto* current_editor = editor_manager->GetCurrentEditor();
232 if (!current_editor || current_editor->type() != yaze::editor::EditorType::kDungeon) {
233 // Auto-switch if possible? Or just fail.
234 return "Error: Dungeon editor is not active. Use 'editor switch dungeon' first.";
235 }
236 auto* dungeon_editor = static_cast<yaze::editor::DungeonEditorV2*>(current_editor);
237
238 if (args.size() > 2 && args[1] == "room") {
239 try {
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)"; }
244 }
245
246 if (args.size() > 2 && args[1] == "select_object") {
247 try {
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)"; }
252 }
253
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");
258 }
259 }
260
261 // Try command registry
262 auto& registry = yaze::cli::CommandRegistry::Instance();
263 if (registry.HasCommand(args[0])) {
264 std::vector<std::string> cmd_args(args.begin() + 1, args.end());
265 // Use the REAL active ROM
266 std::string cmd_output;
267 auto status = registry.Execute(args[0], cmd_args, g_bridge.GetActiveRom(), &cmd_output);
268 if (status.ok()) {
269 return cmd_output.empty() ? "Command executed successfully" : cmd_output;
270 } else {
271 return "Command failed: " + std::string(status.message());
272 }
273 }
274
275 return "Unknown command: " + args[0] + "\nType 'help' for available commands";
276}
277
278// Get command completions for autocomplete
279std::vector<std::string> GetCompletionsInternal(const std::string& partial) {
280 std::vector<std::string> completions;
281
282 // Parse the partial command to understand context
283 auto parts = absl::StrSplit(partial, ' ', absl::SkipEmpty());
284 std::vector<std::string> cmd_parts(parts.begin(), parts.end());
285
286 // If empty or single word, show top-level commands
287 if (cmd_parts.empty() || (cmd_parts.size() == 1 && !partial.empty() && partial.back() != ' ')) {
288 std::string prefix = cmd_parts.empty() ? "" : cmd_parts[0];
289
290 // Get all available commands from registry
291 auto& registry = yaze::cli::CommandRegistry::Instance();
292 auto categories = registry.GetCategories();
293
294 std::set<std::string> unique_commands; // Use set to avoid duplicates
295
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);
301 }
302 }
303 }
304
305 // Add special/built-in commands
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"
310 };
311
312 for (const auto& cmd : special) {
313 if (prefix.empty() || cmd.find(prefix) == 0) {
314 unique_commands.insert(cmd);
315 }
316 }
317
318 // Convert set to vector
319 completions.assign(unique_commands.begin(), unique_commands.end());
320
321 } else if (cmd_parts.size() >= 1) {
322 // Context-specific completions based on the command
323 const std::string& command = cmd_parts[0];
324
325 if (command == "rom") {
326 // ROM subcommands
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);
332 }
333 }
334 }
335 // ... (other completions logic can be kept or expanded via registry in future)
336 }
337
338 // Sort completions alphabetically
339 std::sort(completions.begin(), completions.end());
340
341 return completions;
342}
343
344} // namespace
345
346extern "C" {
347
353EMSCRIPTEN_KEEPALIVE
354const char* Z3edProcessCommand(const char* command) {
355 g_bridge.Initialize();
356
357 if (!command) {
358 g_bridge.last_output = "Invalid command";
359 return g_bridge.last_output.c_str();
360 }
361
362 std::string command_str(command);
363 g_bridge.last_output = ProcessCommandInternal(command_str);
364
365 // Also print to terminal for visual feedback
366 z3ed_print_to_terminal(g_bridge.last_output.c_str());
367
368 return g_bridge.last_output.c_str();
369}
370
376EMSCRIPTEN_KEEPALIVE
377const char* Z3edGetCompletions(const char* partial) {
378 g_bridge.Initialize();
379
380 if (!partial) {
381 g_bridge.last_output = "[]";
382 return g_bridge.last_output.c_str();
383 }
384
385 auto completions = GetCompletionsInternal(std::string(partial));
386
387 // Convert to JSON array
388 std::ostringstream json;
389 json << "[";
390 for (size_t i = 0; i < completions.size(); ++i) {
391 if (i > 0) json << ",";
392 json << "\"" << completions[i] << "\"";
393 }
394 json << "]";
395
396 g_bridge.last_output = json.str();
397 return g_bridge.last_output.c_str();
398}
399
404EMSCRIPTEN_KEEPALIVE
405void Z3edSetApiKey(const char* api_key) {
406 if (api_key) {
407 g_bridge.api_key = std::string(api_key);
408 g_bridge.SetupAIService();
409 }
410}
411
416EMSCRIPTEN_KEEPALIVE
417int Z3edIsReady() {
418 if (!g_bridge.initialized) {
419 g_bridge.Initialize();
420 }
421 return g_bridge.initialized ? 1 : 0;
422}
423
430EMSCRIPTEN_KEEPALIVE
431int Z3edLoadRomData(const uint8_t* data, size_t size) {
432 if (!data || size == 0) {
433 z3ed_error_to_terminal("Invalid ROM data");
434 return 0;
435 }
436
437 // Write to a temporary file
438 std::string temp_path = "/roms/terminal_upload.sfc";
439 std::ofstream file(temp_path, std::ios::binary);
440 if (!file) {
441 z3ed_error_to_terminal("Failed to write to VFS");
442 return 0;
443 }
444 file.write(reinterpret_cast<const char*>(data), size);
445 file.close();
446
447 // Trigger load via bootstrap (which calls Application::LoadRom)
448 yaze::app::wasm::TriggerRomLoad(temp_path);
449
450 z3ed_print_to_terminal("ROM uploaded to VFS. Loading...");
451 return 1;
452}
453
458EMSCRIPTEN_KEEPALIVE
459const char* Z3edGetRomInfo() {
460 yaze::Rom* rom = g_bridge.GetActiveRom();
461
462 if (!rom || !rom->is_loaded()) {
463 g_bridge.last_output = "{\"error\": \"No ROM loaded\"}";
464 return g_bridge.last_output.c_str();
465 }
466
467 std::ostringstream json;
468 json << "{"
469 << "\"loaded\": true,"
470 << "\"size\": " << rom->size() << ","
471 << "\"title\": \"" << rom->title() << "\""
472 << "}";
473
474 g_bridge.last_output = json.str();
475 return g_bridge.last_output.c_str();
476}
477
483EMSCRIPTEN_KEEPALIVE
484const char* Z3edQueryResource(const char* query) {
485 g_bridge.Initialize();
486
487 if (!query) {
488 g_bridge.last_output = "{\"error\": \"Invalid query\"}";
489 return g_bridge.last_output.c_str();
490 }
491
492 yaze::Rom* rom = g_bridge.GetActiveRom();
493
494 if (!rom || !rom->is_loaded()) {
495 g_bridge.last_output = "{\"error\": \"No ROM loaded\"}";
496 return g_bridge.last_output.c_str();
497 }
498
499 // Process resource query using command registry
500 // Map to 'resource-search' which is the actual registered command
501 std::string cmd_name = "resource-search";
502 auto& registry = yaze::cli::CommandRegistry::Instance();
503
504 if (registry.HasCommand(cmd_name)) {
505 // Construct args: resource-search --query <query> --format json
506 std::vector<std::string> cmd_args = {"--query", query, "--format", "json"};
507
508 std::string cmd_output;
509 auto status = registry.Execute(cmd_name, cmd_args, rom, &cmd_output);
510 if (status.ok()) {
511 // If output captured, return it directly
512 if (!cmd_output.empty()) {
513 // We might want to update last_output too as a side effect if the bridge uses it
514 g_bridge.last_output = cmd_output;
515 return g_bridge.last_output.c_str();
516 }
517 return "{\"status\":\"success\", \"message\":\"Query executed but no output returned.\"}";
518 }
519 }
520
521 g_bridge.last_output = "{\"error\": \"Resource query failed\"}";
522 return g_bridge.last_output.c_str();
523}
524
525} // extern "C"
526
527// Emscripten module initialization
528EMSCRIPTEN_BINDINGS(z3ed_terminal) {
529 emscripten::function("processCommand", &Z3edProcessCommand,
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());
540}
541
542#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:24
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
auto title() const
Definition rom.h:133
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()
Definition main.cc:98
Rom * GetGlobalRom()
BrowserAIService * GetGlobalBrowserAIService()
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:167
const char * Z3edProcessCommand(const char *command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)