Update z3ed CLI tool and project build configuration

- Updated `.clang-tidy` and `.clangd` configurations for improved code quality checks and diagnostics.
- Added new submodules for JSON and HTTP libraries to support future features.
- Refined README and documentation files to standardize naming conventions and improve clarity.
- Introduced a new command palette in the CLI for easier command access and execution.
- Implemented various CLI handlers for managing ROM, sprites, palettes, and dungeon functionalities.
- Enhanced the TUI components for better user interaction and command execution.
- Added AI service integration for generating commands based on user prompts, expanding the CLI's capabilities.
This commit is contained in:
scawful
2025-10-01 08:57:10 -04:00
parent e7d4f5ea02
commit ba50d89e7d
46 changed files with 2421 additions and 965 deletions

View File

@@ -0,0 +1,23 @@
#include "cli/service/ai_service.h"
namespace yaze {
namespace cli {
absl::StatusOr<std::vector<std::string>> MockAIService::GetCommands(
const std::string& prompt) {
if (prompt == "Make all the soldiers in Hyrule Castle wear red armor.") {
return std::vector<std::string>{
"palette export --group sprites_aux1 --id 4 --to soldier_palette.col",
"palette set-color --file soldier_palette.col --index 5 --color \"#FF0000\"",
"palette import --group sprites_aux1 --id 4 --from soldier_palette.col"};
} else if (prompt.find("Place a tree at coordinates") != std::string::npos) {
// Example prompt: "Place a tree at coordinates (10, 20) on the light world map"
// For simplicity, we'll hardcode the tile id for a tree
return std::vector<std::string>{"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"};
}
return absl::UnimplementedError("Prompt not supported by mock AI service.");
}
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,28 @@
#ifndef YAZE_SRC_CLI_AI_SERVICE_H_
#define YAZE_SRC_CLI_AI_SERVICE_H_
#include <string>
#include <vector>
#include "absl/status/statusor.h"
namespace yaze {
namespace cli {
class AIService {
public:
virtual ~AIService() = default;
virtual absl::StatusOr<std::vector<std::string>> GetCommands(
const std::string& prompt) = 0;
};
class MockAIService : public AIService {
public:
absl::StatusOr<std::vector<std::string>> GetCommands(
const std::string& prompt) override;
};
} // namespace cli
} // namespace yaze
#endif // YAZE_SRC_CLI_AI_SERVICE_H_

View File

@@ -0,0 +1,87 @@
#include "cli/service/gemini_ai_service.h"
#include <cstdlib>
#include "absl/strings/str_cat.h"
#ifdef YAZE_WITH_JSON
#include "incl/httplib.h"
#include "third_party/json/src/json.hpp"
#endif
namespace yaze {
namespace cli {
GeminiAIService::GeminiAIService(const std::string& api_key) : api_key_(api_key) {}
absl::StatusOr<std::vector<std::string>> GeminiAIService::GetCommands(
const std::string& prompt) {
#ifndef YAZE_WITH_JSON
return absl::UnimplementedError(
"Gemini AI service requires JSON support. Build with -DYAZE_WITH_JSON=ON");
#else
if (api_key_.empty()) {
return absl::FailedPreconditionError("GEMINI_API_KEY not set.");
}
httplib::Client cli("https://generativelanguage.googleapis.com");
nlohmann::json request_body = {
{"contents",
{{"parts",
{{"text",
"You are an expert ROM hacker for The Legend of Zelda: A Link to the Past. "
"Your task is to generate a sequence of `z3ed` CLI commands to achieve the user's request. "
"Respond only with a JSON array of strings, where each string is a `z3ed` command. "
"Do not include any other text or explanation. "
"Available commands: "
"palette export --group <group> --id <id> --to <file>, "
"palette import --group <group> --id <id> --from <file>, "
"palette set-color --file <file> --index <index> --color <hex_color>, "
"overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>. "
"User request: " + prompt}}}}}
};
httplib::Headers headers = {
{"Content-Type", "application/json"},
{"x-goog-api-key", api_key_},
};
auto res = cli.Post("/v1beta/models/gemini-pro:generateContent", headers, request_body.dump(), "application/json");
if (!res) {
return absl::InternalError("Failed to connect to Gemini API.");
}
if (res->status != 200) {
return absl::InternalError(absl::StrCat("Gemini API error: ", res->status, " ", res->body));
}
nlohmann::json response_json = nlohmann::json::parse(res->body);
std::vector<std::string> commands;
try {
for (const auto& candidate : response_json["candidates"]) {
for (const auto& part : candidate["content"]["parts"]) {
std::string text_content = part["text"];
// Assuming the AI returns a JSON array of strings directly in the text content
// This might need more robust parsing depending on actual AI output format
nlohmann::json commands_array = nlohmann::json::parse(text_content);
if (commands_array.is_array()) {
for (const auto& cmd : commands_array) {
if (cmd.is_string()) {
commands.push_back(cmd.get<std::string>());
}
}
}
}
}
} catch (const nlohmann::json::exception& e) {
return absl::InternalError(absl::StrCat("Failed to parse Gemini API response: ", e.what()));
}
return commands;
#endif
}
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,27 @@
#ifndef YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_
#define YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "cli/service/ai_service.h"
namespace yaze {
namespace cli {
class GeminiAIService : public AIService {
public:
explicit GeminiAIService(const std::string& api_key);
absl::StatusOr<std::vector<std::string>> GetCommands(
const std::string& prompt) override;
private:
std::string api_key_;
};
} // namespace cli
} // namespace yaze
#endif // YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_