10#include <unordered_set>
19#include "absl/strings/ascii.h"
20#include "absl/strings/match.h"
21#include "absl/strings/str_format.h"
22#include "absl/time/clock.h"
23#include "absl/time/time.h"
48#include "imgui/misc/cpp/imgui_stdlib.h"
55#include <TargetConditionals.h>
56#include <CoreFoundation/CoreFoundation.h>
57#include <Security/Security.h>
65#if defined(YAZE_WITH_JSON)
66#include "nlohmann/json.hpp"
78 std::strncpy(dest, src.c_str(), N - 1);
82std::filesystem::path ExpandUserPath(
const std::string& input) {
86 if (input.front() !=
'~') {
87 return std::filesystem::path(input);
90 if (home_dir.empty() || home_dir ==
".") {
91 return std::filesystem::path(input);
93 if (input.size() == 1) {
96 if (input[1] ==
'/' || input[1] ==
'\\') {
97 return home_dir / input.substr(2);
99 return home_dir / input.substr(1);
103 const std::string ext = absl::AsciiStrToLower(path.extension().string());
104 return ext ==
".gguf" || ext ==
".ggml" || ext ==
".bin" ||
105 ext ==
".safetensors";
109 std::vector<std::string>* output,
110 std::unordered_set<std::string>* seen) {
111 if (!output || !seen || name.empty()) {
114 if (output->size() >= 512) {
117 if (seen->insert(name).second) {
118 output->push_back(name);
123 if (path.filename() !=
"models") {
126 return path.parent_path().filename() ==
".ollama";
130 std::vector<std::string>* output,
131 std::unordered_set<std::string>* seen) {
132 if (!output || !seen) {
136 const auto library_path =
137 models_root /
"manifests" /
"registry.ollama.ai" /
"library";
138 if (!std::filesystem::exists(library_path, ec)) {
141 std::filesystem::directory_options options =
142 std::filesystem::directory_options::skip_permission_denied;
143 for (std::filesystem::recursive_directory_iterator
144 it(library_path, options, ec),
146 it != end; it.increment(ec)) {
151 if (!it->is_regular_file(ec)) {
154 const auto rel = it->path().lexically_relative(library_path);
158 std::vector<std::string> parts;
159 for (
const auto& part : rel) {
161 parts.push_back(part.string());
167 std::string model = parts.front();
169 for (
size_t i = 1; i < parts.size(); ++i) {
175 const std::string name = tag.empty() ? model : model +
":" + tag;
177 if (output->size() >= 512) {
184 std::vector<std::string>* output,
185 std::unordered_set<std::string>* seen) {
186 if (!output || !seen) {
190 if (!std::filesystem::exists(base_path, ec)) {
193 std::filesystem::directory_options options =
194 std::filesystem::directory_options::skip_permission_denied;
195 constexpr int kMaxDepth = 4;
196 for (std::filesystem::recursive_directory_iterator it(base_path, options, ec),
198 it != end; it.increment(ec)) {
203 if (it->is_directory(ec)) {
204 if (it.depth() >= kMaxDepth) {
205 it.disable_recursion_pending();
209 if (!it->is_regular_file(ec)) {
215 std::filesystem::path rel = it->path().lexically_relative(base_path);
217 rel = it->path().filename();
219 rel.replace_extension();
220 std::string name = rel.generic_string();
222 if (output->size() >= 512) {
230 std::vector<std::string> results;
234 std::unordered_set<std::string> seen;
236 auto expanded = ExpandUserPath(raw_path);
237 if (expanded.empty()) {
241 if (!std::filesystem::exists(expanded, ec)) {
244 if (std::filesystem::is_regular_file(expanded, ec)) {
245 std::filesystem::path rel = expanded.filename();
246 rel.replace_extension();
250 if (!std::filesystem::is_directory(expanded, ec)) {
259 std::sort(results.begin(), results.end());
277 if (api_type ==
"lmstudio") {
280 if (api_type ==
"openai") {
283 if (api_type ==
"gemini") {
286 if (api_type ==
"anthropic") {
300 std::string api_type = host.
api_type;
301 if (api_type ==
"lmstudio") {
304 if (api_type ==
"openai" || api_type ==
"ollama" || api_type ==
"gemini" ||
305 api_type ==
"anthropic") {
308 if (profile->
provider ==
"openai") {
312 if (!api_key.empty()) {
315 }
else if (profile->
provider ==
"ollama") {
319 }
else if (profile->
provider ==
"gemini") {
320 if (!api_key.empty()) {
323 }
else if (profile->
provider ==
"anthropic") {
324 if (!api_key.empty()) {
332 for (
size_t i = 0; i < tags.size(); ++i) {
336 result.append(tags[i]);
341bool ContainsText(
const std::string& haystack,
const std::string& needle) {
342 return haystack.find(needle) != std::string::npos;
346 return text.rfind(prefix, 0) == 0;
350 if (base_url.empty()) {
353 std::string lower = absl::AsciiStrToLower(base_url);
359 if (base_url.empty()) {
362 std::string lower = absl::AsciiStrToLower(base_url);
367 bool allow_insecure) {
368 if (allow_insecure) {
374 if (base_url.empty()) {
377 std::string lower = absl::AsciiStrToLower(base_url);
384#ifndef __EMSCRIPTEN__
386 if (base_url.empty()) {
389 httplib::Client client(base_url);
390 client.set_connection_timeout(0, 200000);
391 client.set_read_timeout(0, 250000);
392 client.set_write_timeout(0, 250000);
393 client.set_follow_location(
true);
394 auto response = client.Get(path);
398 return response->status > 0 && response->status < 500;
409bool ProbeOllamaHost(
const std::string&) {
412bool ProbeOpenAICompatible(
const std::string&) {
418#if defined(__APPLE__)
422 CFStringRef key_ref = CFStringCreateWithCString(
423 kCFAllocatorDefault, key.c_str(), kCFStringEncodingUTF8);
424 const void* keys[] = {kSecClass, kSecAttrAccount, kSecReturnData,
426 const void* values[] = {kSecClassGenericPassword, key_ref, kCFBooleanTrue,
428 CFDictionaryRef query = CFDictionaryCreate(
429 kCFAllocatorDefault, keys, values,
430 static_cast<CFIndex
>(
sizeof(keys) /
sizeof(keys[0])),
431 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
432 CFTypeRef item =
nullptr;
433 OSStatus status = SecItemCopyMatching(query, &item);
440 if (status == errSecItemNotFound) {
443 if (status != errSecSuccess || !item) {
449 CFDataRef data_ref =
static_cast<CFDataRef
>(item);
450 const UInt8* data_ptr = CFDataGetBytePtr(data_ref);
451 CFIndex data_len = CFDataGetLength(data_ref);
452 std::string value(
reinterpret_cast<const char*
>(data_ptr),
453 static_cast<size_t>(data_len));
507 {
"Persona",
"Define persona and goals",
false},
508 {
"Tool Stack",
"Select the agent's tools",
false},
509 {
"Automation",
"Configure automation hooks",
false},
510 {
"Validation",
"Describe E2E validation",
false},
511 {
"E2E Checklist",
"Track readiness for end-to-end runs",
false}};
513 "Describe the persona, tone, and constraints for this agent.";
531 std::make_unique<AgentConfigurationPanel>(
533 panel_manager->RegisterEditorPanel(
535 panel_manager->RegisterEditorPanel(std::make_unique<AgentPromptEditorPanel>(
537 panel_manager->RegisterEditorPanel(std::make_unique<AgentBotProfilesPanel>(
539 panel_manager->RegisterEditorPanel(std::make_unique<AgentBuilderPanel>(
541 panel_manager->RegisterEditorPanel(
542 std::make_unique<AgentChatPanel>(
agent_chat_.get()));
545 panel_manager->RegisterEditorPanel(
546 std::make_unique<AgentKnowledgeBasePanel>([
this]() {
550 ImGui::TextDisabled(
"Knowledge service not available");
552 "Build with Z3ED_AI=ON to enable the knowledge service.");
556 panel_manager->RegisterEditorPanel(std::make_unique<AgentMesenDebugPanel>(
559 panel_manager->RegisterEditorPanel(
560 std::make_unique<MesenScreenshotEditorPanel>(
563 panel_manager->RegisterEditorPanel(
564 std::make_unique<OracleStateLibraryEditorPanel>(
567 panel_manager->RegisterEditorPanel(
568 std::make_unique<FeatureFlagEditorEditorPanel>(
571 panel_manager->RegisterEditorPanel(
572 std::make_unique<ManifestEditorPanel>(
575 panel_manager->RegisterEditorPanel(
576 std::make_unique<SramViewerEditorPanel>(
580 agent_chat_->SetPanelOpener([panel_manager](
const std::string& panel_id) {
581 if (!panel_id.empty()) {
582 panel_manager->ShowPanel(panel_id);
596 const auto& prefs = settings->
prefs();
597 if (prefs.ai_hosts.empty() && prefs.ai_profiles.empty()) {
600 bool applied =
false;
609 if (!prefs.ai_hosts.empty()) {
611 ? prefs.ai_hosts.front().id
612 : prefs.active_ai_host_id;
613 if (!active_id.empty()) {
614 for (
const auto& host : prefs.ai_hosts) {
615 if (host.id == active_id) {
623 if (!prefs.ai_profiles.empty()) {
625 if (!prefs.active_ai_profile.empty()) {
626 for (
const auto& profile : prefs.ai_profiles) {
627 if (profile.name == prefs.active_ai_profile) {
628 active_profile = &profile;
633 if (!active_profile) {
634 active_profile = &prefs.ai_profiles.front();
637 if (!active_profile->
model.empty()) {
647 !prefs.openai_api_key.empty()) {
652 !prefs.gemini_api_key.empty()) {
657 !prefs.anthropic_api_key.empty()) {
678 ?
"http://localhost:11434"
685 ?
"https://api.openai.com"
704 if (std::filesystem::exists(profiles_dir)) {
705 for (
const auto& entry :
706 std::filesystem::directory_iterator(profiles_dir)) {
707 if (entry.path().extension() ==
".json") {
708 std::ifstream file(entry.path());
709 if (file.is_open()) {
710 std::string json_content((std::istreambuf_iterator<char>(file)),
711 std::istreambuf_iterator<char>());
713 if (profile_or.ok()) {
720 return absl::OkStatus();
731 return absl::OkStatus();
738 return absl::OkStatus();
749 bool profile_updated =
false;
750 auto env_value = [](
const char* key) -> std::string {
751 const char* value = std::getenv(key);
752 return value ? std::string(value) : std::string();
755 std::string env_openai_base = env_value(
"OPENAI_BASE_URL");
756 if (env_openai_base.empty()) {
757 env_openai_base = env_value(
"OPENAI_API_BASE");
759 std::string env_openai_model = env_value(
"OPENAI_MODEL");
760 std::string env_ollama_host = env_value(
"OLLAMA_HOST");
761 std::string env_ollama_model = env_value(
"OLLAMA_MODEL");
762 std::string env_gemini_model = env_value(
"GEMINI_MODEL");
763 std::string env_anthropic_model = env_value(
"ANTHROPIC_MODEL");
765 if (!env_ollama_host.empty() &&
769 profile_updated =
true;
771 if (!env_openai_base.empty()) {
775 "https://api.openai.com") {
778 profile_updated =
true;
782 if (
const char* gemini_key = std::getenv(
"GEMINI_API_KEY")) {
785 profile_updated =
true;
788 if (
const char* anthropic_key = std::getenv(
"ANTHROPIC_API_KEY")) {
791 profile_updated =
true;
794 if (
const char* openai_key = std::getenv(
"OPENAI_API_KEY")) {
797 profile_updated =
true;
800 bool provider_is_default =
803 if (provider_is_default) {
809 env_gemini_model.empty() ?
"gemini-2.5-flash" : env_gemini_model;
812 profile_updated =
true;
818 ?
"claude-3-5-sonnet-20241022"
819 : env_anthropic_model;
822 profile_updated =
true;
824 !env_openai_base.empty()) {
828 if (!env_openai_model.empty()) {
835 profile_updated =
true;
836 }
else if (!env_ollama_host.empty() || !env_ollama_model.empty()) {
843 profile_updated =
true;
848 !env_ollama_model.empty()) {
851 profile_updated =
true;
854 !env_openai_model.empty()) {
857 profile_updated =
true;
863 profile_updated =
true;
866 !env_gemini_model.empty()) {
869 profile_updated =
true;
871 if (profile_updated) {
876 profile_updated =
true;
880 agent_chat_->Initialize(toast_manager, proposal_drawer);
891 harness_telemetry_bridge_.SetAgentChat(
agent_chat_.get());
944 ?
"http://localhost:11434"
949 ctx_config.openai_base_url =
962 CopyStringToBuffer(ctx_config.ai_provider, ctx_config.provider_buffer);
963 CopyStringToBuffer(ctx_config.ai_model, ctx_config.model_buffer);
964 CopyStringToBuffer(ctx_config.ollama_host, ctx_config.ollama_host_buffer);
965 CopyStringToBuffer(ctx_config.gemini_api_key, ctx_config.gemini_key_buffer);
966 CopyStringToBuffer(ctx_config.anthropic_api_key,
967 ctx_config.anthropic_key_buffer);
968 CopyStringToBuffer(ctx_config.openai_api_key, ctx_config.openai_key_buffer);
969 CopyStringToBuffer(ctx_config.openai_base_url,
970 ctx_config.openai_base_url_buffer);
983 const std::string prev_provider = ctx_config.
ai_provider;
984 const std::string prev_openai_base = ctx_config.openai_base_url;
985 const std::string prev_ollama_host = ctx_config.ollama_host;
986 ctx_config.ai_provider =
988 ctx_config.ai_model = config.
ai_model;
989 ctx_config.ollama_host = config.
ollama_host.empty() ?
"http://localhost:11434"
994 ctx_config.openai_base_url =
996 ctx_config.host_id = config.
host_id;
997 ctx_config.verbose = config.
verbose;
1002 ctx_config.top_p = config.
top_p;
1011 if (prev_provider != ctx_config.ai_provider ||
1012 prev_openai_base != ctx_config.openai_base_url ||
1013 prev_ollama_host != ctx_config.ollama_host) {
1016 model_cache.model_names.clear();
1017 model_cache.last_refresh = absl::InfinitePast();
1018 model_cache.auto_refresh_requested =
false;
1019 model_cache.last_provider = ctx_config.ai_provider;
1020 model_cache.last_openai_base = ctx_config.openai_base_url;
1021 model_cache.last_ollama_host = ctx_config.ollama_host;
1024 CopyStringToBuffer(ctx_config.ai_provider, ctx_config.provider_buffer);
1025 CopyStringToBuffer(ctx_config.ai_model, ctx_config.model_buffer);
1026 CopyStringToBuffer(ctx_config.ollama_host, ctx_config.ollama_host_buffer);
1027 CopyStringToBuffer(ctx_config.gemini_api_key, ctx_config.gemini_key_buffer);
1028 CopyStringToBuffer(ctx_config.anthropic_api_key,
1029 ctx_config.anthropic_key_buffer);
1030 CopyStringToBuffer(ctx_config.openai_api_key, ctx_config.openai_key_buffer);
1031 CopyStringToBuffer(ctx_config.openai_base_url,
1032 ctx_config.openai_base_url_buffer);
1084 prefs.
resources = tool_config.resources;
1085 prefs.
dungeon = tool_config.dungeon;
1086 prefs.
overworld = tool_config.overworld;
1087 prefs.
messages = tool_config.messages;
1088 prefs.
dialogue = tool_config.dialogue;
1089 prefs.
gui = tool_config.gui;
1090 prefs.
music = tool_config.music;
1091 prefs.
sprite = tool_config.sprite;
1092#ifdef YAZE_WITH_GRPC
1093 prefs.
emulator = tool_config.emulator;
1098 service->SetToolPreferences(prefs);
1107 if (model_cache.loading) {
1110 if (!force && model_cache.last_refresh != absl::InfinitePast()) {
1111 absl::Duration since_refresh = absl::Now() - model_cache.
last_refresh;
1112 if (since_refresh < absl::Seconds(15)) {
1117 model_cache.loading =
true;
1118 model_cache.auto_refresh_requested =
true;
1119 model_cache.available_models.clear();
1120 model_cache.model_names.clear();
1123 bool needs_local_refresh = force;
1124 if (!needs_local_refresh) {
1126 needs_local_refresh =
true;
1129 needs_local_refresh =
true;
1132 if (needs_local_refresh) {
1133 model_cache.local_model_names = CollectLocalModelNames(&prefs);
1138 model_cache.local_model_names.clear();
1143 next_key.
provider = config.ai_provider.empty() ?
"mock" : config.ai_provider;
1144 next_key.
model = config.ai_model;
1151 next_key.
verbose = config.verbose;
1163 model_cache.loading =
false;
1164 model_cache.model_names = model_cache.local_model_names;
1165 model_cache.last_refresh = absl::Now();
1181 if (!service_or.ok()) {
1183 model_cache.loading =
false;
1184 model_cache.model_names = model_cache.local_model_names;
1185 model_cache.last_refresh = absl::Now();
1197 if (!models_or.ok()) {
1198 model_cache.loading =
false;
1199 model_cache.model_names = model_cache.local_model_names;
1200 model_cache.last_refresh = absl::Now();
1208 model_cache.available_models = models_or.value();
1209 std::unordered_set<std::string> seen;
1210 for (
const auto& info : model_cache.available_models) {
1211 if (!info.name.empty()) {
1212 AddUniqueModelName(info.name, &model_cache.model_names, &seen);
1215 std::sort(model_cache.model_names.begin(), model_cache.model_names.end());
1218 std::string selected;
1219 for (
const auto& info : model_cache.available_models) {
1220 if (ctx_config.ai_provider.empty() ||
1221 info.provider == ctx_config.ai_provider) {
1222 selected = info.name;
1226 if (selected.empty() && !model_cache.model_names.empty()) {
1227 selected = model_cache.model_names.front();
1229 if (!selected.empty()) {
1231 CopyStringToBuffer(ctx_config.ai_model, ctx_config.model_buffer);
1234 model_cache.last_refresh = absl::Now();
1235 model_cache.loading =
false;
1247 if (!preset.
model.empty()) {
1248 config.ai_model = preset.
model;
1250 if (!preset.
host.empty()) {
1251 if (config.ai_provider ==
"ollama") {
1252 config.ollama_host = preset.
host;
1253 }
else if (config.ai_provider ==
"openai") {
1258 for (
auto& entry : config.model_presets) {
1259 if (entry.name == preset.
name) {
1260 entry.last_used = absl::Now();
1265 CopyStringToBuffer(config.ai_provider, config.provider_buffer);
1266 CopyStringToBuffer(config.ai_model, config.model_buffer);
1267 CopyStringToBuffer(config.ollama_host, config.ollama_host_buffer);
1268 CopyStringToBuffer(config.openai_base_url, config.openai_base_url_buffer);
1283 const auto& prefs = settings->
prefs();
1284 if (prefs.ai_hosts.empty()) {
1293 auto resolved = host;
1294 if (resolved.api_key.empty()) {
1295 resolved.api_key = ResolveHostApiKey(&prefs, host);
1312 bool probe_only_local) {
1314 if (api_type ==
"lmstudio") {
1315 api_type =
"openai";
1317 auto resolved = build_host(host);
1318 bool has_key = !resolved.api_key.empty();
1320 if (api_type ==
"ollama") {
1321 if (resolved.base_url.empty()) {
1324 if (probe_only_local && !IsLocalOrTrustedEndpoint(
1325 resolved.base_url, resolved.allow_insecure)) {
1328 if (!ProbeOllamaHost(resolved.base_url)) {
1331 return select_host(resolved);
1334 if (api_type ==
"openai") {
1335 if (resolved.base_url.empty()) {
1339 IsLocalOrTrustedEndpoint(resolved.base_url, resolved.allow_insecure);
1340 if (probe_only_local && !trusted) {
1343 if (trusted && ProbeOpenAICompatible(resolved.base_url)) {
1344 return select_host(resolved);
1346 if (!probe_only_local && has_key) {
1347 return select_host(resolved);
1352 if (api_type ==
"gemini") {
1356 return select_host(resolved);
1359 if (api_type ==
"anthropic") {
1363 return select_host(resolved);
1369 std::vector<const UserSettings::Preferences::AiHost*> candidates;
1370 if (!prefs.active_ai_host_id.empty()) {
1371 for (
const auto& host : prefs.ai_hosts) {
1372 if (host.
id == prefs.active_ai_host_id) {
1373 candidates.push_back(&host);
1378 for (
const auto& host : prefs.ai_hosts) {
1379 if (!candidates.empty() && candidates.front()->id == host.
id) {
1382 candidates.push_back(&host);
1385 for (
const auto* host : candidates) {
1386 if (try_host(*host,
true)) {
1390 for (
const auto* host : candidates) {
1391 if (try_host(*host,
false)) {
1405 ImGuiIO& imgui_io = ImGui::GetIO();
1417 ImGui::TextDisabled(
"Agent configuration unavailable.");
1418 ImGui::TextWrapped(
"Initialize the Agent UI to edit provider settings.");
1426 if (!prefs.ai_hosts.empty()) {
1428 theme.accent_color);
1429 const auto& hosts = prefs.ai_hosts;
1431 ? prefs.active_ai_host_id
1433 int active_index = -1;
1434 for (
size_t i = 0; i < hosts.size(); ++i) {
1435 if (!active_id.empty() && hosts[i].id == active_id) {
1436 active_index =
static_cast<int>(i);
1440 const char* preview = (active_index >= 0)
1441 ? hosts[active_index].label.c_str()
1443 if (ImGui::BeginCombo(
"##ai_host_preset", preview)) {
1444 for (
size_t i = 0; i < hosts.size(); ++i) {
1445 const bool selected = (
static_cast<int>(i) == active_index);
1446 if (ImGui::Selectable(hosts[i].label.c_str(), selected)) {
1452 ImGui::SetItemDefaultFocus();
1457 if (active_index >= 0) {
1458 const auto& host = hosts[active_index];
1459 ImGui::TextDisabled(
"Active host: %s", host.label.c_str());
1460 ImGui::TextDisabled(
"Endpoint: %s", host.base_url.c_str());
1461 ImGui::TextDisabled(
"API type: %s", host.api_type.empty()
1463 : host.api_type.c_str());
1465 ImGui::TextDisabled(
1466 "Host presets come from settings.json (Documents/Yaze).");
1497 if (ImGui::BeginChild(
"AgentStatusCard", ImVec2(0, 150),
true)) {
1499 theme.accent_color);
1502 if (ImGui::BeginTable(
"AgentStatusTable", 2,
1503 ImGuiTableFlags_SizingStretchProp)) {
1504 ImGui::TableNextRow();
1505 ImGui::TableSetColumnIndex(0);
1506 ImGui::TextDisabled(
"Chat");
1507 ImGui::TableSetColumnIndex(1);
1512 if (ImGui::SmallButton(
"Open")) {
1517 ImGui::TableNextRow();
1518 ImGui::TableSetColumnIndex(0);
1519 ImGui::TextDisabled(
"Provider");
1520 ImGui::TableSetColumnIndex(1);
1527 ImGui::TableNextRow();
1528 ImGui::TableSetColumnIndex(0);
1529 ImGui::TextDisabled(
"ROM");
1530 ImGui::TableSetColumnIndex(1);
1532 ImGui::TextColored(theme.status_success,
1535 ImGui::TextDisabled(
"Tools ready");
1537 ImGui::TextColored(theme.status_warning,
ICON_MD_WARNING " Not Loaded");
1546 if (ImGui::BeginChild(
"AgentMetricsCard", ImVec2(0, 170),
true)) {
1548 theme.accent_color);
1550 auto metrics =
agent_chat_->GetAgentService()->GetMetrics();
1551 ImGui::TextDisabled(
"Messages: %d user / %d agent",
1552 metrics.total_user_messages,
1553 metrics.total_agent_messages);
1554 ImGui::TextDisabled(
"Tool calls: %d Proposals: %d Commands: %d",
1555 metrics.total_tool_calls, metrics.total_proposals,
1556 metrics.total_commands);
1557 ImGui::TextDisabled(
"Avg latency: %.2fs Elapsed: %.2fs",
1558 metrics.average_latency_seconds,
1559 metrics.total_elapsed_seconds);
1561 std::vector<double> latencies;
1562 for (
const auto& msg :
agent_chat_->GetAgentService()->GetHistory()) {
1564 msg.model_metadata.has_value() &&
1565 msg.model_metadata->latency_seconds > 0.0) {
1566 latencies.push_back(msg.model_metadata->latency_seconds);
1569 if (latencies.size() > 30) {
1570 latencies.erase(latencies.begin(),
1571 latencies.end() -
static_cast<long>(30));
1573 if (!latencies.empty()) {
1574 std::vector<double> xs(latencies.size());
1575 for (
size_t i = 0; i < xs.size(); ++i) {
1576 xs[i] =
static_cast<double>(i);
1578 ImPlotFlags plot_flags = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus |
1579 ImPlotFlags_NoBoxSelect;
1580 if (ImPlot::BeginPlot(
"##LatencyPlot", ImVec2(-1, 90), plot_flags)) {
1581 ImPlot::SetupAxes(
nullptr,
nullptr, ImPlotAxisFlags_NoDecorations,
1582 ImPlotAxisFlags_NoDecorations);
1583 ImPlot::SetupAxisLimits(ImAxis_X1, 0, xs.back(), ImGuiCond_Always);
1584 double max_latency =
1585 *std::max_element(latencies.begin(), latencies.end());
1586 ImPlot::SetupAxisLimits(ImAxis_Y1, 0, max_latency * 1.2,
1588 ImPlot::PlotLine(
"Latency", xs.data(), latencies.data(),
1589 static_cast<int>(latencies.size()));
1594 ImGui::TextDisabled(
"Initialize the chat system to see metrics.");
1604 ImGui::TextDisabled(
"View detailed metrics in the Metrics tab");
1611 theme.accent_color);
1613 ImGui::Text(
"File:");
1614 ImGui::SetNextItemWidth(-45);
1616 const char* options[] = {
"system_prompt.txt",
"system_prompt_v2.txt",
1617 "system_prompt_v3.txt"};
1618 for (
const char* option : options) {
1620 if (ImGui::Selectable(option, selected)) {
1632 if (ImGui::IsItemHovered()) {
1633 ImGui::SetTooltip(
"Reload from disk");
1639 if (content_result.ok()) {
1649 std::string placeholder = absl::StrFormat(
1650 "# System prompt file not found: %s\n"
1652 "# Ensure the file exists in assets/agent/%s\n",
1662 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
1663 ImGui::GetContentRegionAvail().y - 60);
1667 if (ImGui::Button(
ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) {
1678 "Edit the system prompt that guides the agent's behavior. Changes are "
1679 "stored on the active bot profile.");
1685 theme.accent_color);
1688 ImGui::BeginChild(
"CurrentProfile", ImVec2(0, 150),
true);
1690 theme.accent_color);
1696 ImGui::TextWrapped(
"Description: %s",
1704 if (ImGui::Button(
ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) {
1706 new_profile.
name =
"New Profile";
1719 theme.accent_color);
1721 ImGui::BeginChild(
"ProfilesList", ImVec2(0, 0),
true);
1723 ImGui::TextDisabled(
1724 "No saved profiles. Create and save a profile to see it here.");
1728 ImGui::PushID(
static_cast<int>(i));
1731 ImVec2 button_size(ImGui::GetContentRegionAvail().x - 80, 0);
1732 ImVec4 button_color =
1733 is_current ? theme.accent_color : theme.panel_bg_darker;
1739 absl::StrFormat(
"Loaded profile: %s", profile.name),
1755 absl::StrFormat(
"Deleted profile: %s", profile.name),
1765 ImGui::TextDisabled(
" %s | %s", profile.provider.c_str(),
1766 profile.description.empty()
1768 : profile.description.c_str());
1779 theme.accent_color);
1800 ImGui::BeginChild(
"HistoryList", ImVec2(0, 0),
true);
1802 ImGui::TextDisabled(
1803 "No chat history. Start a conversation in the chat window.");
1808 from_user ? theme.user_message_color : theme.agent_message_color;
1813 ImGui::TextDisabled(
"%s", absl::FormatTime(
"%H:%M:%S", msg.timestamp,
1814 absl::LocalTimeZone())
1817 ImGui::TextWrapped(
"%s", msg.message.c_str());
1828 theme.accent_color);
1832 auto metrics =
agent_chat_->GetAgentService()->GetMetrics();
1833 if (ImGui::BeginTable(
"MetricsTable", 2,
1834 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1835 ImGui::TableSetupColumn(
"Metric", ImGuiTableColumnFlags_WidthFixed,
1837 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
1838 ImGui::TableHeadersRow();
1840 auto Row = [](
const char* label,
const std::string& value) {
1841 ImGui::TableNextRow();
1842 ImGui::TableSetColumnIndex(0);
1843 ImGui::Text(
"%s", label);
1844 ImGui::TableSetColumnIndex(1);
1845 ImGui::TextDisabled(
"%s", value.c_str());
1848 Row(
"Total Messages",
1849 absl::StrFormat(
"%d user / %d agent", metrics.total_user_messages,
1850 metrics.total_agent_messages));
1851 Row(
"Tool Calls", absl::StrFormat(
"%d", metrics.total_tool_calls));
1852 Row(
"Commands", absl::StrFormat(
"%d", metrics.total_commands));
1853 Row(
"Proposals", absl::StrFormat(
"%d", metrics.total_proposals));
1854 Row(
"Average Latency (s)",
1855 absl::StrFormat(
"%.2f", metrics.average_latency_seconds));
1857 absl::StrFormat(
"%.2f", metrics.total_elapsed_seconds));
1862 ImGui::TextDisabled(
"Initialize the chat system to see metrics.");
1869 theme.accent_color);
1873 "Customize the tile reference file that AI uses for tile placement. "
1874 "Organize tiles by category and provide hex IDs with descriptions.");
1891 if (ImGui::Button(
ICON_MD_SAVE " Save", ImVec2(100, 0))) {
1894 " Save to project directory (coming soon)",
1903 if (ImGui::IsItemHovered()) {
1904 ImGui::SetTooltip(
"Reload from disk");
1912 std::string default_tiles =
1913 "# Common Tile16 Reference\n"
1914 "# Format: 0xHEX = Description\n\n"
1916 "0x020 = Grass (standard)\n\n"
1918 "0x02E = Tree (oak)\n"
1921 "0x14C = Water (top edge)\n"
1922 "0x14D = Water (middle)\n";
1932 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
1933 ImGui::GetContentRegionAvail().y);
1941 theme.accent_color);
1945 "Create a custom system prompt from scratch or start from a template.");
1948 ImGui::Text(
"Prompt Name:");
1949 ImGui::SetNextItemWidth(-1);
1950 ImGui::InputTextWithHint(
"##new_prompt_name",
"e.g., custom_prompt.txt",
1954 ImGui::Text(
"Start from template:");
1956 auto LoadTemplate = [&](
const char* path,
const char* label) {
1957 if (ImGui::Button(label, ImVec2(-1, 0))) {
1969 LoadTemplate(
"agent/system_prompt_v2.txt",
1971 LoadTemplate(
"agent/system_prompt_v3.txt",
1976 std::string blank_template =
1977 "# Custom System Prompt\n\n"
1978 "You are an AI assistant for ROM hacking.\n\n"
1980 "- Help users understand ROM data\n"
1981 "- Provide accurate information\n"
1982 "- Use tools when needed\n\n"
1984 "1. Always provide text_response after tool calls\n"
1985 "2. Be helpful and accurate\n"
1986 "3. Explain your reasoning\n";
2000 if (ImGui::Button(
ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
2003 if (!absl::EndsWith(filename,
".txt")) {
2008 absl::StrFormat(
ICON_MD_SAVE " Prompt saved as %s", filename),
2021 "Note: New prompts are saved to your project. Use the Prompt Editor to "
2022 "edit existing prompts.");
2028 theme.accent_color);
2031 ImGui::TextDisabled(
"Chat system not initialized.");
2035 ImGui::BeginChild(
"AgentBuilderPanel", ImVec2(0, 0),
false);
2040 int completed_stages = 0;
2042 if (stage.completed) {
2046 float completion_ratio =
2049 :
static_cast<float>(completed_stages) /
2051 auto truncate_summary = [](
const std::string& text) {
2052 constexpr size_t kMaxLen = 64;
2053 if (text.size() <= kMaxLen) {
2056 return text.substr(0, kMaxLen - 3) +
"...";
2059 const float left_width =
2060 std::min(260.0f, ImGui::GetContentRegionAvail().x * 0.32f);
2062 ImGui::BeginChild(
"BuilderStages", ImVec2(left_width, 0),
true);
2064 ImGui::TextDisabled(
"%d/%zu complete", completed_stages,
2066 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0));
2071 ImGui::PushID(
static_cast<int>(i));
2073 if (ImGui::Selectable(stage.name.c_str(), selected)) {
2075 stage_index =
static_cast<int>(i);
2077 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 24.0f);
2078 ImGui::Checkbox(
"##stage_done", &stage.completed);
2079 ImGui::TextDisabled(
"%s", truncate_summary(stage.summary).c_str());
2087 ImGui::BeginChild(
"BuilderDetails", ImVec2(0, 0),
false);
2089 theme.accent_color);
2090 if (stage_index >= 0 &&
2092 ImGui::TextColored(theme.text_secondary_color,
"%s",
2097 switch (stage_index) {
2099 static std::string new_goal;
2100 ImGui::Text(
"Persona + Goals");
2102 "Define the agent's voice, boundaries, and success criteria. Keep "
2103 "goals short and action-focused.");
2104 ImGui::InputTextMultiline(
"##persona_notes",
2107 ImGui::TextDisabled(
"Add Goal");
2108 ImGui::InputTextWithHint(
"##goal_input",
2109 "e.g. Review collision edge cases", &new_goal);
2111 if (ImGui::Button(
ICON_MD_ADD) && !new_goal.empty()) {
2118 ImGui::PushID(
static_cast<int>(i));
2129 ImGui::Text(
"Tool Stack");
2131 "Enable only what the plan needs. Fewer tools = clearer responses.");
2132 auto tool_checkbox = [&](
const char* label,
bool* value,
2134 ImGui::Checkbox(label, value);
2136 ImGui::TextDisabled(
"%s", hint);
2139 "Project files, docs, refs");
2141 "Rooms, objects, entrances");
2143 "Maps, tile16, entities");
2145 "NPC text + scripts");
2147 "Test harness + screenshots");
2150 "Sprites + palettes");
2154 "RAM/SRAM watch + inspection");
2158 ImGui::Text(
"Automation");
2160 "Use automation to validate fixes quickly. Pair with gRPC harness "
2161 "for repeatable checks.");
2164 ImGui::Checkbox(
"Auto-focus proposal drawer",
2169 ImGui::Text(
"Validation Criteria");
2171 "Capture the acceptance criteria and what a passing run looks like.");
2172 ImGui::InputTextMultiline(
"##validation_notes",
2178 ImGui::Text(
"E2E Checklist");
2179 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0),
2180 absl::StrFormat(
"%d/%zu complete", completed_stages,
2183 ImGui::Checkbox(
"Ready for automation handoff",
2185 ImGui::TextDisabled(
"Auto-sync ROM: %s",
2187 ImGui::TextDisabled(
"Auto-focus proposals: %s",
2195 ImGui::TextDisabled(
"Builder Output");
2196 ImGui::BulletText(
"Persona notes sync to the chat summary");
2197 ImGui::BulletText(
"Tool stack applies to the agent tool preferences");
2198 ImGui::BulletText(
"E2E readiness gates automation handoff");
2202 if (ImGui::Button(
ICON_MD_LINK " Apply to Chat", ImVec2(-1, 0))) {
2213#ifdef YAZE_WITH_GRPC
2217 service->SetToolPreferences(prefs);
2219 auto agent_cfg = service->GetConfig();
2224 service->SetConfig(agent_cfg);
2236 ImGui::InputTextWithHint(
"##blueprint_path",
2237 "Path to blueprint (optional)...",
2239 std::filesystem::path blueprint_path =
2241 ? (std::filesystem::temp_directory_path() /
"agent_builder.json")
2278 ImGui::TextDisabled(
"Mesen2 debug panel unavailable.");
2286 ImGui::TextDisabled(
"Mesen2 screenshot panel unavailable.");
2298 ImGui::TextDisabled(
"Oracle state panel unavailable.");
2308 ImGui::TextDisabled(
"Feature flag panel unavailable.");
2317 ImGui::TextDisabled(
"Manifest panel unavailable.");
2326 ImGui::TextDisabled(
"SRAM viewer panel unavailable.");
2331 const std::filesystem::path& path) {
2332#if defined(YAZE_WITH_JSON)
2333 nlohmann::json json;
2351 json[
"stages"] = nlohmann::json::array();
2353 json[
"stages"].push_back({{
"name", stage.name},
2354 {
"summary", stage.summary},
2355 {
"completed", stage.completed}});
2359 std::filesystem::create_directories(path.parent_path(), ec);
2360 std::ofstream file(path);
2361 if (!file.is_open()) {
2362 return absl::InternalError(
2363 absl::StrFormat(
"Failed to open blueprint: %s", path.string()));
2365 file << json.dump(2);
2367 return absl::OkStatus();
2370 return absl::UnimplementedError(
"Blueprint export requires JSON support");
2375 const std::filesystem::path& path) {
2376#if defined(YAZE_WITH_JSON)
2377 std::ifstream file(path);
2378 if (!file.is_open()) {
2379 return absl::NotFoundError(
2380 absl::StrFormat(
"Blueprint not found: %s", path.string()));
2383 nlohmann::json json;
2388 if (json.contains(
"goals") && json[
"goals"].is_array()) {
2389 for (
const auto& goal : json[
"goals"]) {
2390 if (goal.is_string()) {
2395 if (json.contains(
"tools") && json[
"tools"].is_object()) {
2396 auto tools = json[
"tools"];
2406 tools.value(
"memory_inspector",
false);
2411 json.value(
"auto_focus_proposals",
true);
2413 if (json.contains(
"stages") && json[
"stages"].is_array()) {
2415 for (
const auto& stage : json[
"stages"]) {
2417 builder_stage.
name = stage.value(
"name", std::string{});
2418 builder_stage.
summary = stage.value(
"summary", std::string{});
2419 builder_stage.
completed = stage.value(
"completed",
false);
2424 return absl::OkStatus();
2427 return absl::UnimplementedError(
"Blueprint import requires JSON support");
2432#if defined(YAZE_WITH_JSON)
2434 if (!dir_status.ok())
2437 std::filesystem::path profile_path =
2439 std::ofstream file(profile_path);
2440 if (!file.is_open()) {
2441 return absl::InternalError(
"Failed to open profile file for writing");
2448 return absl::UnimplementedError(
2449 "JSON support required for profile management");
2454#if defined(YAZE_WITH_JSON)
2455 std::filesystem::path profile_path =
2457 if (!std::filesystem::exists(profile_path)) {
2458 return absl::NotFoundError(absl::StrFormat(
"Profile '%s' not found", name));
2461 std::ifstream file(profile_path);
2462 if (!file.is_open()) {
2463 return absl::InternalError(
"Failed to open profile file");
2466 std::string json_content((std::istreambuf_iterator<char>(file)),
2467 std::istreambuf_iterator<char>());
2470 if (!profile_or.ok()) {
2471 return profile_or.status();
2493 return absl::OkStatus();
2495 return absl::UnimplementedError(
2496 "JSON support required for profile management");
2501 std::filesystem::path profile_path =
2503 if (!std::filesystem::exists(profile_path)) {
2504 return absl::NotFoundError(absl::StrFormat(
"Profile '%s' not found", name));
2507 std::filesystem::remove(profile_path);
2538 const std::filesystem::path& path) {
2539#if defined(YAZE_WITH_JSON)
2544 std::ofstream file(path);
2545 if (!file.is_open()) {
2546 return absl::InternalError(
"Failed to open file for export");
2549 return absl::OkStatus();
2553 return absl::UnimplementedError(
"JSON support required");
2558#if defined(YAZE_WITH_JSON)
2559 if (!std::filesystem::exists(path)) {
2560 return absl::NotFoundError(
"Import file not found");
2563 std::ifstream file(path);
2564 if (!file.is_open()) {
2565 return absl::InternalError(
"Failed to open import file");
2568 std::string json_content((std::istreambuf_iterator<char>(file)),
2569 std::istreambuf_iterator<char>());
2572 if (!profile_or.ok()) {
2573 return profile_or.status();
2579 return absl::UnimplementedError(
"JSON support required");
2585 if (agent_dir.ok()) {
2586 return *agent_dir /
"profiles";
2589 if (temp_dir.ok()) {
2590 return *temp_dir /
"agent" /
"profiles";
2592 return std::filesystem::current_path() /
"agent" /
"profiles";
2598 std::filesystem::create_directories(dir, ec);
2600 return absl::InternalError(absl::StrFormat(
2601 "Failed to create profiles directory: %s", ec.message()));
2603 return absl::OkStatus();
2607#if defined(YAZE_WITH_JSON)
2608 nlohmann::json json;
2609 json[
"name"] = profile.
name;
2611 json[
"provider"] = profile.
provider;
2612 json[
"host_id"] = profile.
host_id;
2613 json[
"model"] = profile.
model;
2620 json[
"verbose"] = profile.
verbose;
2625 json[
"top_p"] = profile.
top_p;
2628 json[
"tags"] = profile.
tags;
2629 json[
"created_at"] = absl::FormatTime(absl::RFC3339_full, profile.
created_at,
2630 absl::UTCTimeZone());
2631 json[
"modified_at"] = absl::FormatTime(
2632 absl::RFC3339_full, profile.
modified_at, absl::UTCTimeZone());
2634 return json.dump(2);
2641 const std::string& json_str)
const {
2642#if defined(YAZE_WITH_JSON)
2644 nlohmann::json json = nlohmann::json::parse(json_str);
2647 profile.
name = json.value(
"name",
"Unnamed Profile");
2648 profile.
description = json.value(
"description",
"");
2649 profile.
provider = json.value(
"provider",
"mock");
2650 profile.
host_id = json.value(
"host_id",
"");
2651 profile.
model = json.value(
"model",
"");
2652 profile.
ollama_host = json.value(
"ollama_host",
"http://localhost:11434");
2657 json.value(
"openai_base_url",
"https://api.openai.com");
2659 profile.
verbose = json.value(
"verbose",
false);
2663 profile.
temperature = json.value(
"temperature", 0.25f);
2664 profile.
top_p = json.value(
"top_p", 0.95f);
2668 if (json.contains(
"tags") && json[
"tags"].is_array()) {
2669 for (
const auto& tag : json[
"tags"]) {
2670 profile.
tags.push_back(tag.get<std::string>());
2674 if (json.contains(
"created_at")) {
2676 if (absl::ParseTime(absl::RFC3339_full,
2677 json[
"created_at"].get<std::string>(), &created,
2683 if (json.contains(
"modified_at")) {
2684 absl::Time modified;
2685 if (absl::ParseTime(absl::RFC3339_full,
2686 json[
"modified_at"].get<std::string>(), &modified,
2693 }
catch (
const std::exception& e) {
2694 return absl::InternalError(
2695 absl::StrFormat(
"Failed to parse profile JSON: %s", e.what()));
2698 return absl::UnimplementedError(
"JSON support required");
2724 auto status = service->ConfigureProvider(provider_config);
2729 auto agent_cfg = service->GetConfig();
2732 agent_cfg.verbose = config.
verbose;
2734 service->SetConfig(agent_cfg);
2765 if (!session_or.ok())
2766 return session_or.status();
2780 absl::StrFormat(
"Hosting local session: %s", session_name),
2786#ifdef YAZE_WITH_GRPC
2788 if (!network_coordinator_) {
2789 return absl::FailedPreconditionError(
2790 "Network coordinator not initialized. Connect to a server first.");
2793 const char* username = std::getenv(
"USER");
2795 username = std::getenv(
"USERNAME");
2798 username =
"unknown";
2801 auto session_or = network_coordinator_->HostSession(session_name, username);
2802 if (!session_or.ok())
2803 return session_or.status();
2817 absl::StrFormat(
"Hosting network session: %s", session_name),
2825 return absl::InvalidArgumentError(
"Unsupported collaboration mode");
2834 if (!session_or.ok())
2835 return session_or.status();
2849 absl::StrFormat(
"Joined local session: %s", session_code),
2856#ifdef YAZE_WITH_GRPC
2858 if (!network_coordinator_) {
2859 return absl::FailedPreconditionError(
2860 "Network coordinator not initialized. Connect to a server first.");
2863 const char* username = std::getenv(
"USER");
2865 username = std::getenv(
"USERNAME");
2868 username =
"unknown";
2871 auto session_or = network_coordinator_->JoinSession(session_code, username);
2872 if (!session_or.ok())
2873 return session_or.status();
2887 absl::StrFormat(
"Joined network session: %s", session_code),
2895 return absl::InvalidArgumentError(
"Unsupported collaboration mode");
2900 return absl::FailedPreconditionError(
"Not in a session");
2908#ifdef YAZE_WITH_GRPC
2910 if (network_coordinator_) {
2911 auto status = network_coordinator_->LeaveSession();
2927 return absl::OkStatus();
2932 return absl::FailedPreconditionError(
"Not in a session");
2937 if (!session_or.ok())
2938 return session_or.status();
2957#ifdef YAZE_WITH_GRPC
2958 using yaze::test::CaptureActiveWindow;
2959 using yaze::test::CaptureHarnessScreenshot;
2960 using yaze::test::CaptureWindowByName;
2962 absl::StatusOr<yaze::test::ScreenshotArtifact> result;
2963 switch (config.
mode) {
2965 result = CaptureHarnessScreenshot(
"");
2968 result = CaptureActiveWindow(
"");
2970 result = CaptureHarnessScreenshot(
"");
2977 result = CaptureActiveWindow(
"");
2980 result = CaptureHarnessScreenshot(
"");
2987 return result.status();
2989 *output_path = result->file_path;
2990 return absl::OkStatus();
2994 return absl::UnimplementedError(
"Screenshot capture requires YAZE_WITH_GRPC");
2999 const std::string& prompt) {
3000#ifdef YAZE_WITH_GRPC
3002 ? std::getenv(
"GEMINI_API_KEY")
3004 if (!api_key || std::strlen(api_key) == 0) {
3005 return absl::FailedPreconditionError(
3006 "Gemini API key not configured (set GEMINI_API_KEY)");
3018 if (!response.ok()) {
3019 return response.status();
3025 auto history = service->GetHistory();
3028 agent_msg.
message = response->text_response;
3030 history.push_back(agent_msg);
3031 service->ReplaceHistory(history);
3039 return absl::OkStatus();
3043 return absl::UnimplementedError(
"Gemini integration requires YAZE_WITH_GRPC");
3047#ifdef YAZE_WITH_GRPC
3048absl::Status AgentEditor::ConnectToServer(
const std::string& server_url) {
3050 network_coordinator_ =
3051 std::make_unique<NetworkCollaborationCoordinator>(server_url);
3055 absl::StrFormat(
"Connected to server: %s", server_url),
3059 return absl::OkStatus();
3060 }
catch (
const std::exception& e) {
3061 return absl::InternalError(
3062 absl::StrFormat(
"Failed to connect to server: %s", e.what()));
3066void AgentEditor::DisconnectFromServer() {
3070 network_coordinator_.reset();
3077bool AgentEditor::IsConnectedToServer()
const {
3078 return network_coordinator_ && network_coordinator_->IsConnected();
3092 return std::nullopt;
static absl::StatusOr< std::string > LoadTextFile(const std::string &relative_path)
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< AgentResponse > GenerateMultimodalResponse(const std::string &, const std::string &)
std::string current_session_id_
void RefreshModelCache(bool force)
std::unique_ptr< cli::AIService > model_service_
void SetCurrentProfile(const BotProfile &profile)
void ApplyConfig(const AgentConfig &config)
void InitializeWithDependencies(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, Rom *rom)
absl::Status SaveBuilderBlueprint(const std::filesystem::path &path)
void DrawAdvancedMetricsPanel()
absl::StatusOr< SessionInfo > JoinSession(const std::string &session_code, CollaborationMode mode=CollaborationMode::kLocal)
void SetRomContext(Rom *rom)
void SetChatActive(bool active)
void SetupAutomationCallbacks()
std::unique_ptr< MesenScreenshotPanel > mesen_screenshot_panel_
void SyncContextFromProfile()
KnowledgePanelCallback knowledge_panel_callback_
absl::StatusOr< BotProfile > JsonToProfile(const std::string &json) const
void ApplyToolPreferencesFromContext()
std::unique_ptr< OracleStateLibraryPanel > oracle_state_panel_
absl::StatusOr< SessionInfo > RefreshSession()
absl::Status ImportProfile(const std::filesystem::path &path)
bool IsChatActive() const
void SetupMultimodalCallbacks()
void ApplyModelPreset(const ModelPreset &preset)
bool prompt_editor_initialized_
absl::Time last_local_model_scan_
void SyncConfigFromProfile()
void Initialize() override
ProposalDrawer * proposal_drawer_
std::vector< cli::agent::ChatMessage > cached_history_
AgentBuilderState builder_state_
AgentConfig current_config_
ModelServiceKey last_model_service_key_
absl::Status Save() override
bool common_tiles_initialized_
std::string current_session_name_
CollaborationMode GetCurrentMode() const
void DrawPromptEditorPanel()
void DrawFeatureFlagPanel()
std::unique_ptr< FeatureFlagEditorPanel > feature_flag_panel_
absl::Status SendToGemini(const std::filesystem::path &image_path, const std::string &prompt)
std::unique_ptr< AgentConfigPanel > config_panel_
absl::Status EnsureProfilesDirectory()
void SyncProfileUiState()
void SetContext(AgentUIContext *context)
std::unique_ptr< MesenDebugPanel > mesen_debug_panel_
void DrawSramViewerPanel()
AgentUIContext * context_
bool MaybeAutoDetectLocalProviders()
absl::Status LoadBuilderBlueprint(const std::filesystem::path &path)
bool history_needs_refresh_
ProfileUiState profile_ui_state_
absl::Status ExportProfile(const BotProfile &profile, const std::filesystem::path &path)
void DrawNewPromptCreator()
void DrawOracleStatePanel()
CollaborationMode current_mode_
absl::Status SaveBotProfile(const BotProfile &profile)
std::string active_prompt_file_
absl::Status DeleteBotProfile(const std::string &name)
std::filesystem::path GetProfilesDirectory() const
ToastManager * toast_manager_
AgentConfig GetCurrentConfig() const
std::unique_ptr< TextEditor > common_tiles_editor_
absl::StatusOr< SessionInfo > HostSession(const std::string &session_name, CollaborationMode mode=CollaborationMode::kLocal)
void DrawCommonTilesEditor()
void DrawMesenScreenshotPanel()
char new_prompt_name_[128]
std::string ProfileToJson(const BotProfile &profile) const
absl::Status LeaveSession()
std::unique_ptr< TextEditor > prompt_editor_
std::unique_ptr< AgentCollaborationCoordinator > local_coordinator_
void DrawMesenDebugPanel()
std::unique_ptr< SramViewerPanel > sram_viewer_panel_
absl::Status CaptureSnapshot(std::filesystem::path *output_path, const CaptureConfig &config)
absl::Status Load() override
BotProfile current_profile_
absl::Status LoadBotProfile(const std::string &name)
std::vector< BotProfile > GetAllProfiles() const
void DrawChatHistoryViewer()
std::vector< std::string > current_participants_
void DrawBotProfilesPanel()
std::vector< std::string > last_local_model_paths_
void ApplyConfigFromContext(const AgentConfigState &config)
void MarkProfileUiDirty()
std::unique_ptr< AgentChat > agent_chat_
absl::Status Update() override
void DrawConfigurationPanel()
void ApplyUserSettingsDefaults(bool force=false)
std::unique_ptr< ManifestPanel > manifest_panel_
std::vector< BotProfile > loaded_profiles_
std::optional< SessionInfo > GetCurrentSession() const
void DrawAgentBuilderPanel()
Unified context for agent UI components.
ModelCache & model_cache()
AgentConfigState & agent_config()
EditorContext context() const
EditorDependencies dependencies_
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
ImGui drawer for displaying and managing agent proposals.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
RAII guard for ImGui style colors.
static TestManager & Get()
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_FILE_COPY
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DELETE_FOREVER
#define ICON_MD_ANALYTICS
absl::StatusOr< std::unique_ptr< AIService > > CreateAIServiceStrict(const AIServiceConfig &config)
std::string NormalizeOpenAiBaseUrl(std::string base)
void RenderStatusIndicator(const char *label, bool active)
bool StyledButton(const char *label, const ImVec4 &color, const ImVec2 &size)
const AgentUITheme & GetTheme()
void RenderSectionHeader(const char *icon, const char *label, const ImVec4 &color)
void RenderProviderBadge(const char *provider)
bool IsLocalOrTrustedEndpoint(const std::string &base_url, bool allow_insecure)
void ApplyHostPresetToProfile(AgentEditor::BotProfile *profile, const UserSettings::Preferences::AiHost &host, const UserSettings::Preferences *prefs)
void CollectOllamaManifestModels(const std::filesystem::path &models_root, std::vector< std::string > *output, std::unordered_set< std::string > *seen)
void CopyStringToBuffer(const std::string &src, char(&dest)[N])
void CollectModelFiles(const std::filesystem::path &base_path, std::vector< std::string > *output, std::unordered_set< std::string > *seen)
bool IsLocalOpenAiBaseUrl(const std::string &base_url)
std::string BuildTagsString(const std::vector< std::string > &tags)
std::string ResolveHostApiKey(const UserSettings::Preferences *prefs, const UserSettings::Preferences::AiHost &host)
std::optional< std::string > LoadKeychainValue(const std::string &key)
bool ProbeOllamaHost(const std::string &base_url)
bool IsTailscaleEndpoint(const std::string &base_url)
bool HasModelExtension(const std::filesystem::path &path)
bool ContainsText(const std::string &haystack, const std::string &needle)
bool IsOllamaModelsPath(const std::filesystem::path &path)
void AddUniqueModelName(const std::string &name, std::vector< std::string > *output, std::unordered_set< std::string > *seen)
bool StartsWithText(const std::string &text, const std::string &prefix)
bool ProbeOpenAICompatible(const std::string &base_url)
bool ProbeHttpEndpoint(const std::string &base_url, const char *path)
std::vector< std::string > CollectLocalModelNames(const UserSettings::Preferences *prefs)
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
static const LanguageDefinition & CPlusPlus()
std::string gemini_api_key
std::string openai_base_url
std::string anthropic_api_key
std::string openai_api_key
std::function< void(const AgentConfigState &) update_config)
std::function< void(bool force)> refresh_models
std::function< void()> apply_tool_preferences
std::function< void(const ModelPreset &) apply_preset)
Agent configuration state.
std::vector< std::string > model_chain
std::string anthropic_api_key
std::string openai_base_url
std::string gemini_api_key
std::vector< ModelPreset > model_presets
std::vector< std::string > favorite_models
std::string openai_api_key
struct yaze::editor::AgentEditor::AgentBuilderState::ToolPlan tools
std::string persona_notes
std::vector< std::string > goals
std::vector< Stage > stages
bool auto_focus_proposals
std::string blueprint_path
std::string anthropic_api_key
std::string gemini_api_key
std::string openai_api_key
std::string openai_base_url
std::string anthropic_api_key
std::vector< std::string > tags
std::string openai_api_key
std::string openai_base_url
std::string gemini_api_key
std::string system_prompt
std::string specific_window_name
std::string openai_api_key
std::string anthropic_api_key
std::string openai_base_url
std::string gemini_api_key
std::vector< std::string > participants
project::YazeProject * project
UserSettings * user_settings
PanelManager * panel_manager
std::vector< cli::ModelInfo > available_models
Model preset for quick switching.
std::string credential_id
std::string gemini_api_key
std::string active_ai_host_id
std::string anthropic_api_key
std::string openai_api_key
std::vector< std::string > ai_model_paths