feat: Add resource search and dungeon room description commands

- Implemented `resource-search` command to allow fuzzy searching of resource labels.
- Added `dungeon-describe-room` command to summarize metadata for a specified dungeon room.
- Enhanced `agent` command handler to support new commands and updated usage documentation.
- Introduced read-only accessors for room metadata in the Room class.
- Updated AI service to recognize and handle new commands for resource searching and room description.
- Improved metrics tracking for user interactions, including command execution and response times.
- Enhanced TUI to display command metrics and session summaries.
This commit is contained in:
scawful
2025-10-04 12:00:51 -04:00
parent acada1bec5
commit 4b61b213c0
15 changed files with 844 additions and 68 deletions

View File

@@ -3,6 +3,7 @@
#include <algorithm>
#include <cctype>
#include <sstream>
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
@@ -51,6 +52,38 @@ std::string ExtractRoomId(const std::string& normalized_prompt) {
return "0x000";
}
std::string ExtractKeyword(const std::string& normalized_prompt) {
static const char* kStopwords[] = {
"search", "for", "resource", "resources", "label", "labels",
"please", "the", "a", "an", "list", "of", "in", "find"};
auto is_stopword = [](const std::string& word) {
for (const char* stop : kStopwords) {
if (word == stop) {
return true;
}
}
return false;
};
std::istringstream stream(normalized_prompt);
std::string token;
while (stream >> token) {
token.erase(std::remove_if(token.begin(), token.end(), [](unsigned char c) {
return !std::isalnum(c) && c != '_' && c != '-';
}),
token.end());
if (token.empty()) {
continue;
}
if (!is_stopword(token)) {
return token;
}
}
return "all";
}
} // namespace
absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
@@ -96,6 +129,20 @@ absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
return response;
}
if (absl::StrContains(normalized, "search") &&
(absl::StrContains(normalized, "resource") ||
absl::StrContains(normalized, "label"))) {
ToolCall call;
call.tool_name = "resource-search";
call.args.emplace("query", ExtractKeyword(normalized));
response.text_response =
"Let me look through the labelled resources for matches.";
response.reasoning =
"Resource search provides fuzzy matching against the ROM label catalogue.";
response.tool_calls.push_back(call);
return response;
}
if (absl::StrContains(normalized, "sprite") &&
absl::StrContains(normalized, "room")) {
ToolCall call;
@@ -109,6 +156,19 @@ absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
return response;
}
if (absl::StrContains(normalized, "describe") &&
absl::StrContains(normalized, "room")) {
ToolCall call;
call.tool_name = "dungeon-describe-room";
call.args.emplace("room", ExtractRoomId(normalized));
response.text_response =
"I'll summarize the room's metadata and hazards.";
response.reasoning =
"Room description tool surfaces lighting, effects, and object counts before planning edits.";
response.tool_calls.push_back(call);
return response;
}
response.text_response =
"I'm just a mock service. Please load a provider like ollama or gemini.";
return response;

View File

@@ -123,11 +123,13 @@ void GeminiAIService::EnableFunctionCalling(bool enable) {
std::vector<std::string> GeminiAIService::GetAvailableTools() const {
return {
"resource_list",
"dungeon_list_sprites",
"overworld_find_tile",
"overworld_describe_map",
"overworld_list_warps"
"resource-list",
"resource-search",
"dungeon-list-sprites",
"dungeon-describe-room",
"overworld-find-tile",
"overworld-describe-map",
"overworld-list-warps"
};
}