#include "panels.h" #include "../core.h" #include "../../icons.h" #include "core/filesystem.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace afs { namespace viz { std::filesystem::path ResolveHafsScawfulRoot() { const char* env_root = std::getenv("AFS_SCAWFUL_ROOT"); if (env_root && env_root[0] != '\0') { auto path = studio::core::FileSystem::ResolvePath(env_root); if (studio::core::FileSystem::Exists(path)) { return path; } } auto plugin_path = studio::core::FileSystem::ResolvePath("~/.config/afs/plugins/afs_scawful"); if (studio::core::FileSystem::Exists(plugin_path)) { return plugin_path; } const char* trunk_env = std::getenv("TRUNK_ROOT"); if (trunk_env && trunk_env[0] != '\0') { auto trunk_root = studio::core::FileSystem::ResolvePath(trunk_env); auto candidate = trunk_root / "scawful" / "research" / "afs_scawful"; if (studio::core::FileSystem::Exists(candidate)) { return candidate; } } auto fallback_path = studio::core::FileSystem::ResolvePath("~/src/trunk/scawful/research/afs_scawful"); if (studio::core::FileSystem::Exists(fallback_path)) { return fallback_path; } return {}; } namespace ui { static bool ContainsInsensitive(const std::string& str, const std::string& pattern) { if (pattern.empty()) return true; auto it = std::search(str.begin(), str.end(), pattern.begin(), pattern.end(), [](char ch1, char ch2) { return std::tolower(static_cast(ch1)) == std::tolower(static_cast(ch2)); }); return it != str.end(); } static std::string TrimCopy(const std::string& input) { auto start = input.find_first_not_of(" \t\r\n"); if (start == std::string::npos) return ""; auto end = input.find_last_not_of(" \t\r\n"); return input.substr(start, end - start + 1); } static std::string UnquoteCopy(const std::string& input) { if (input.size() >= 2 && input.front() == '"' && input.back() == '"') { return input.substr(1, input.size() - 2); } return input; } struct HackOverrideEdit { bool initialized = false; bool enabled = false; bool dirty = false; std::string review_status; float weight = 1.0f; bool weight_set = false; bool notes_set = false; bool include_set = false; bool exclude_set = false; std::string notes; std::vector include_globs; std::vector exclude_globs; }; struct NoteBufferState { std::array buffer{}; bool initialized = false; }; static bool LoadOverrideFile(const std::filesystem::path& path, std::unordered_map* edits, std::string* error) { edits->clear(); if (error) error->clear(); if (!studio::core::FileSystem::Exists(path)) { if (error) *error = "Overrides file not found"; return false; } auto content_opt = studio::core::FileSystem::ReadFile(path); if (!content_opt) { if (error) *error = "Failed to read overrides file"; return false; } std::istringstream stream(*content_opt); std::string line; HackOverrideEdit current; std::string current_name; auto flush = [&]() { if (current_name.empty()) return; current.initialized = true; current.enabled = true; (*edits)[current_name] = current; }; while (std::getline(stream, line)) { std::string trimmed = TrimCopy(line); if (trimmed.empty() || trimmed[0] == '#') continue; if (trimmed == "[[hack]]") { flush(); current = HackOverrideEdit{}; current_name.clear(); continue; } auto sep = trimmed.find('='); if (sep == std::string::npos) continue; std::string key = TrimCopy(trimmed.substr(0, sep)); std::string value = TrimCopy(trimmed.substr(sep + 1)); if (key == "name") { current_name = UnquoteCopy(value); } else if (key == "review_status") { current.review_status = UnquoteCopy(value); } else if (key == "notes") { current.notes = UnquoteCopy(value); current.notes_set = true; } else if (key == "weight") { try { current.weight = std::stof(value); current.weight_set = true; } catch (...) { // Ignore parse errors } } else if (key == "include_globs" || key == "exclude_globs") { std::vector items; if (!value.empty() && value.front() == '[' && value.back() == ']') { std::string inner = value.substr(1, value.size() - 2); std::stringstream list_stream(inner); std::string token; while (std::getline(list_stream, token, ',')) { std::string cleaned = UnquoteCopy(TrimCopy(token)); if (!cleaned.empty()) items.push_back(cleaned); } } if (key == "include_globs") { current.include_globs = std::move(items); current.include_set = true; } else { current.exclude_globs = std::move(items); current.exclude_set = true; } } } flush(); return true; } template static void FillBuffer(std::array& buffer, const std::string& text) { buffer.fill('\0'); size_t copy_len = std::min(buffer.size() - 1, text.size()); std::memcpy(buffer.data(), text.data(), copy_len); buffer[copy_len] = '\0'; } static std::string JoinLines(const std::vector& lines) { std::ostringstream out; for (size_t i = 0; i < lines.size(); ++i) { out << lines[i]; if (i + 1 < lines.size()) out << "\n"; } return out.str(); } static std::vector SplitLines(const std::string& input) { std::vector lines; std::istringstream stream(input); std::string line; while (std::getline(stream, line)) { std::string trimmed = TrimCopy(line); if (!trimmed.empty()) lines.push_back(trimmed); } return lines; } static std::string EscapeTomlString(const std::string& value) { std::string out; out.reserve(value.size()); for (char ch : value) { if (ch == '"') out += "\\\""; else out += ch; } return out; } static void WriteStringArray(std::ostringstream& output, const std::string& key, const std::vector& values) { output << key << " = ["; for (size_t i = 0; i < values.size(); ++i) { output << "\"" << EscapeTomlString(values[i]) << "\""; if (i + 1 < values.size()) output << ", "; } output << "]\n"; } static bool RunPythonScript(const std::filesystem::path& script_path, const std::filesystem::path& module_root, std::string* output) { if (output) output->clear(); if (!studio::core::FileSystem::Exists(script_path)) { if (output) *output = "Script not found"; return false; } std::string cmd; if (!module_root.empty()) { cmd = "AFS_SCAWFUL_ROOT=\"" + module_root.string() + "\" "; cmd += "PYTHONPATH=\"" + module_root.string() + "\" "; } cmd += "python3 \"" + script_path.string() + "\" 2>&1"; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { if (output) *output = "Failed to launch script"; return false; } char buffer[256]; std::ostringstream result; while (fgets(buffer, sizeof(buffer), pipe)) { result << buffer; } int status = pclose(pipe); if (output) *output = result.str(); return status == 0; } void RenderInspectorPanel(AppState& state, const DataLoader& loader, ImFont* font_header, const std::string& data_path) { const auto& trends = loader.GetQualityTrends(); const auto& coverage = loader.GetCoverage(); const auto& runs = loader.GetTrainingRuns(); const auto& generators = loader.GetGeneratorStats(); if (font_header) ImGui::PushFont(font_header); ImGui::Text(ICON_MD_INSIGHTS " INSPECTOR"); if (font_header) ImGui::PopFont(); ImGui::Separator(); if (state.inspector_context != PlotKind::None) { ImGui::PushStyleColor(ImGuiCol_Text, GetThemeColor(ImGuiCol_PlotLines, state.current_theme)); ImGui::Text(ICON_MD_SETTINGS " CHART PROPERTIES"); ImGui::PopStyleColor(); const auto& options = PlotOptions(); const char* label = "Unknown Plot"; for (const auto& opt : options) { if (opt.kind == state.inspector_context) { label = opt.label; break; } } ImGui::Text("Subject: %s", label); ImGui::Separator(); ImGui::Checkbox("Show Markers", &state.show_plot_markers); ImGui::Checkbox("Show Legend", &state.show_plot_legends); ImGui::SliderFloat("Line Weight", &state.line_weight, 1.0f, 5.0f); if (state.inspector_context == PlotKind::QualityTrends) { ImGui::Spacing(); ImGui::TextDisabled("Domain Filter"); for (auto& [domain, visible] : state.domain_visibility) { ImGui::Checkbox(domain.c_str(), &visible); } } if (ImGui::Button("Reset Context")) state.inspector_context = PlotKind::None; ImGui::Separator(); } ImGui::TextDisabled("Data Snapshot"); ImGui::Text("Runs: %zu", runs.size()); ImGui::Text("Generators: %zu", generators.size()); ImGui::Text("Regions: %zu", loader.GetEmbeddingRegions().size()); ImGui::Text("Sparse Regions: %d", coverage.sparse_regions); ImGui::Text("Data Path:"); ImGui::TextWrapped("%s", data_path.c_str()); const auto& status = loader.GetLastStatus(); ImGui::Text("Sources: %d/%d ok", status.OkCount(), status.FoundCount()); if (status.error_count > 0) { ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f), "Load Errors: %d", status.error_count); if (!status.last_error.empty()) { ImGui::TextDisabled("Last Error:"); ImGui::TextWrapped("%s (%s)", status.last_error.c_str(), status.last_error_source.empty() ? "unknown" : status.last_error_source.c_str()); } } float avg_quality = 0.0f; if (!trends.empty()) { for (const auto& t : trends) avg_quality += t.mean; avg_quality /= static_cast(trends.size()); } ImGui::Spacing(); ImGui::TextDisabled("Health Signals"); ImGui::ProgressBar(avg_quality, ImVec2(-1, 0), "Avg Quality"); ImGui::ProgressBar(coverage.coverage_score, ImVec2(-1, 0), "Coverage Score"); ImGui::Separator(); ImGui::TextDisabled("Selected Run"); if (state.selected_run_index >= 0 && state.selected_run_index < static_cast(runs.size())) { const auto& run = runs[state.selected_run_index]; ImGui::Text("%s", run.run_id.c_str()); if (!run.model_name.empty()) ImGui::Text("Model: %s", run.model_name.c_str()); if (!run.base_model.empty()) ImGui::Text("Base: %s", run.base_model.c_str()); ImGui::Text("Samples: %d", run.samples_count); ImGui::Text("Final Loss: %.5f", run.final_loss); if (!run.start_time.empty() || !run.end_time.empty()) { ImGui::Text("Window: %s -> %s", run.start_time.empty() ? "?" : run.start_time.c_str(), run.end_time.empty() ? "?" : run.end_time.c_str()); } if (!run.dataset_path.empty()) { ImGui::Text("Dataset:"); ImGui::TextWrapped("%s", run.dataset_path.c_str()); } if (!run.notes.empty()) { ImGui::Text("Notes:"); ImGui::TextWrapped("%s", run.notes.c_str()); } if (!run.domain_distribution.empty()) { std::vector> domains(run.domain_distribution.begin(), run.domain_distribution.end()); std::sort(domains.begin(), domains.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); if (domains.size() > 6) domains.resize(6); std::vector labels; std::vector values; std::vector label_storage; for (const auto& [domain, count] : domains) { label_storage.push_back(domain); values.push_back(static_cast(count)); } for (const auto& label : label_storage) labels.push_back(label.c_str()); ImPlotFlags plot_flags = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus; ApplyPremiumPlotStyles("##RunDomains", state); if (ImPlot::BeginPlot("##RunDomains", ImVec2(-1, 140), plot_flags)) { ImPlotAxisFlags axis_flags = static_cast(GetPlotAxisFlags(state)); ImPlot::SetupAxes("Domain", "Samples", axis_flags, axis_flags); ImPlot::SetupAxisTicks(ImAxis_X1, 0, static_cast(labels.size() - 1), static_cast(labels.size()), labels.data()); ImPlot::SetNextFillStyle(GetSeriesColor(2), 0.75f); ImPlot::PlotBars("Samples", values.data(), static_cast(values.size()), 0.6); ImPlot::EndPlot(); } ImPlot::PopStyleColor(2); ImPlot::PopStyleVar(6); } if (!run.eval_metrics.empty()) { std::vector labels; std::vector values; std::vector label_storage; for (const auto& [metric, value] : run.eval_metrics) { label_storage.push_back(metric); values.push_back(value); } for (const auto& label : label_storage) labels.push_back(label.c_str()); ImPlotFlags plot_flags = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus; ApplyPremiumPlotStyles("##RunMetrics", state); if (ImPlot::BeginPlot("##RunMetrics", ImVec2(-1, 120), plot_flags)) { ImPlotAxisFlags axis_flags = static_cast(GetPlotAxisFlags(state)); ImPlot::SetupAxes("Metric", "Score", axis_flags, axis_flags); ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 1.0, ImPlotCond_Once); ImPlot::SetupAxisTicks(ImAxis_X1, 0, static_cast(labels.size() - 1), static_cast(labels.size()), labels.data()); ImPlot::SetNextFillStyle(GetSeriesColor(4), 0.75f); ImPlot::PlotBars("Score", values.data(), static_cast(values.size()), 0.6); ImPlot::EndPlot(); } ImPlot::PopStyleColor(2); ImPlot::PopStyleVar(6); } } else { ImGui::TextDisabled("Select a training run in the Dataset panel."); } ImGui::Separator(); ImGui::TextDisabled("Selected Generator"); if (state.selected_generator_index >= 0 && state.selected_generator_index < static_cast(generators.size())) { const auto& gen = generators[state.selected_generator_index]; ImGui::Text("%s", gen.name.c_str()); ImGui::Text("Accepted: %d", gen.samples_accepted); ImGui::Text("Rejected: %d", gen.samples_rejected); ImGui::Text("Avg Quality: %.3f", gen.avg_quality); ImGui::ProgressBar(gen.acceptance_rate, ImVec2(-1, 0), "Acceptance Rate"); if (!gen.rejection_reasons.empty()) { std::vector> reasons(gen.rejection_reasons.begin(), gen.rejection_reasons.end()); std::sort(reasons.begin(), reasons.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); if (reasons.size() > 6) reasons.resize(6); std::vector labels; std::vector values; std::vector label_storage; for (const auto& [reason, count] : reasons) { std::string formatted = reason; std::replace(formatted.begin(), formatted.end(), '_', ' '); label_storage.push_back(formatted); values.push_back(static_cast(count)); } for (const auto& label : label_storage) labels.push_back(label.c_str()); ImPlotFlags plot_flags = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus; ApplyPremiumPlotStyles("##GenRejections", state); if (ImPlot::BeginPlot("##GenRejections", ImVec2(-1, 120), plot_flags)) { ImPlotAxisFlags axis_flags = static_cast(GetPlotAxisFlags(state)); ImPlot::SetupAxes("Reason", "Count", axis_flags, axis_flags); ImPlot::SetupAxisTicks(ImAxis_X1, 0, static_cast(labels.size() - 1), static_cast(labels.size()), labels.data()); ImPlot::SetNextFillStyle(GetSeriesColor(7), 0.75f); ImPlot::PlotBars("Count", values.data(), static_cast(values.size()), 0.6); ImPlot::EndPlot(); } ImPlot::PopStyleColor(2); ImPlot::PopStyleVar(6); } } else { ImGui::TextDisabled("Select a generator in the Dataset panel."); } ImGui::Separator(); ImGui::Spacing(); if (font_header) ImGui::PushFont(font_header); ImGui::Text(ICON_MD_FILE_DOWNLOAD_DONE " DATA INTEGRITY AUDIT"); if (font_header) ImGui::PopFont(); ImGui::Separator(); auto render_audit_item = [](const char* name, bool found, bool ok, const char* error) { ImGui::BeginGroup(); if (!found) { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), ICON_MD_FILE_OPEN " %s", name); ImGui::SameLine(); ImGui::TextDisabled("(Not Found)"); } else if (!ok) { ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), ICON_MD_REPORT_GMAILERRORRED " %s", name); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), "(Error)"); if (error && error[0] != '\0') { ImGui::Indent(); ImGui::TextDisabled("%s", error); ImGui::Unindent(); } } else { ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECK_CIRCLE " %s", name); ImGui::SameLine(); ImGui::TextDisabled("(Synced)"); } ImGui::EndGroup(); }; render_audit_item("quality_feedback.json", status.quality_found, status.quality_ok, status.quality_ok ? "" : status.last_error.c_str()); render_audit_item("active_learning.json", status.active_found, status.active_ok, status.active_ok ? "" : status.last_error.c_str()); render_audit_item("training_feedback.json", status.training_found, status.training_ok, status.training_ok ? "" : status.last_error.c_str()); ImGui::Spacing(); ImGui::TextDisabled("Integrity Score: %.1f%%", (status.OkCount() / (float)std::max(1, status.FoundCount())) * 100.0f); ImGui::ProgressBar(status.OkCount() / (float)std::max(1, status.FoundCount()), ImVec2(-1, 0)); } void RenderDatasetPanel(AppState& state, const DataLoader& loader) { const auto& runs = loader.GetTrainingRuns(); const auto& generators = loader.GetGeneratorStats(); const auto& coverage = loader.GetCoverage(); const auto& dataset_registry = loader.GetDatasetRegistry(); const auto& dataset_error = loader.GetDatasetRegistryError(); if (ImGui::BeginTabBar("DatasetTabs")) { if (ImGui::BeginTabItem("Training Runs")) { ImGui::InputTextWithHint("##RunFilter", "Filter by run ID or model", state.run_filter.data(), state.run_filter.size()); ImGui::SameLine(); if (ImGui::Button("Clear")) state.run_filter[0] = '\0'; if (ImGui::BeginTable("RunTable", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupColumn("Run ID", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Model", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Samples", ImGuiTableColumnFlags_WidthFixed, 90); ImGui::TableSetupColumn("Loss", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Domains", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableHeadersRow(); for (size_t i = 0; i < runs.size(); ++i) { const auto& run = runs[i]; if (!ContainsInsensitive(run.run_id, state.run_filter.data()) && !ContainsInsensitive(run.model_name, state.run_filter.data())) continue; bool selected = static_cast(i) == state.selected_run_index; ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::Selectable(run.run_id.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) { state.selected_run_index = static_cast(i); state.selected_run_id = run.run_id; } ImGui::TableNextColumn(); ImGui::Text("%s", run.model_name.empty() ? "-" : run.model_name.c_str()); ImGui::TableNextColumn(); ImGui::Text("%d", run.samples_count); ImGui::TableNextColumn(); ImGui::Text("%.4f", run.final_loss); ImGui::TableNextColumn(); ImGui::Text("%zu", run.domain_distribution.size()); } ImGui::EndTable(); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Data Sources")) { const auto& resource_index = loader.GetResourceIndex(); const auto& resource_error = loader.GetResourceIndexError(); static std::string resource_status; if (!resource_error.empty()) { ImGui::TextColored(ImVec4(0.9f, 0.5f, 0.2f, 1.0f), "%s", resource_error.c_str()); } if (resource_index.total_files == 0) { ImGui::TextDisabled("No resource index loaded."); } else { ImGui::Text("Total files: %d", resource_index.total_files); ImGui::Text("Duplicates: %d", resource_index.duplicates_found); if (!resource_index.indexed_at.empty()) { ImGui::TextDisabled("Indexed at: %s", resource_index.indexed_at.c_str()); } } ImGui::Spacing(); if (ImGui::Button("Rebuild Resource Index")) { auto scawful_root = ResolveHafsScawfulRoot(); std::filesystem::path script_path = scawful_root.empty() ? std::filesystem::current_path() / "rebuild_resource_index.py" : scawful_root / "scripts" / "rebuild_resource_index.py"; std::string build_output; bool ok = RunPythonScript(script_path, scawful_root, &build_output); if (!build_output.empty()) { resource_status = ok ? "Resource index rebuilt (see logs)" : "Resource index rebuild failed (see logs)"; } else { resource_status = ok ? "Resource index rebuilt" : "Resource index rebuild failed"; } state.should_refresh = true; } if (!resource_status.empty()) { ImGui::SameLine(); ImGui::TextDisabled("%s", resource_status.c_str()); } if (!resource_index.by_source.empty()) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextDisabled("Sources"); std::vector> sources; sources.reserve(resource_index.by_source.size()); for (const auto& [name, count] : resource_index.by_source) { sources.emplace_back(name, count); } std::sort(sources.begin(), sources.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); if (ImGui::BeginTable("ResourceSources", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch, 1.5f); ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Share", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableHeadersRow(); for (const auto& entry : sources) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", entry.first.c_str()); ImGui::TableNextColumn(); ImGui::Text("%d", entry.second); ImGui::TableNextColumn(); float share = resource_index.total_files > 0 ? static_cast(entry.second) / static_cast(resource_index.total_files) : 0.0f; ImGui::Text("%.1f%%", share * 100.0f); } ImGui::EndTable(); } } if (!resource_index.by_type.empty()) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextDisabled("File Types"); std::vector> types; types.reserve(resource_index.by_type.size()); for (const auto& [name, count] : resource_index.by_type) { types.emplace_back(name, count); } std::sort(types.begin(), types.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); if (ImGui::BeginTable("ResourceTypes", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch, 1.5f); ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Share", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableHeadersRow(); for (const auto& entry : types) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", entry.first.c_str()); ImGui::TableNextColumn(); ImGui::Text("%d", entry.second); ImGui::TableNextColumn(); float share = resource_index.total_files > 0 ? static_cast(entry.second) / static_cast(resource_index.total_files) : 0.0f; ImGui::Text("%.1f%%", share * 100.0f); } ImGui::EndTable(); } } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Datasets")) { static std::string dataset_status; if (!dataset_error.empty()) { ImGui::TextColored(ImVec4(0.9f, 0.5f, 0.2f, 1.0f), "%s", dataset_error.c_str()); } ImGui::InputTextWithHint("##DatasetFilter", "Filter by dataset name", state.dataset_filter.data(), state.dataset_filter.size()); ImGui::SameLine(); if (ImGui::Button("Clear##DatasetFilter")) state.dataset_filter[0] = '\0'; ImGui::SameLine(); if (ImGui::Button("Rebuild Dataset Registry")) { auto scawful_root = ResolveHafsScawfulRoot(); std::filesystem::path script_path = scawful_root.empty() ? std::filesystem::current_path() / "build_dataset_registry.py" : scawful_root / "scripts" / "build_dataset_registry.py"; std::string build_output; bool ok = RunPythonScript(script_path, scawful_root, &build_output); if (!build_output.empty()) { dataset_status = ok ? "Dataset registry rebuilt (see logs)" : "Dataset registry rebuild failed (see logs)"; } else { dataset_status = ok ? "Dataset registry rebuilt" : "Dataset registry rebuild failed"; } state.should_refresh = true; } if (!dataset_status.empty()) { ImGui::SameLine(); ImGui::TextDisabled("%s", dataset_status.c_str()); } if (dataset_registry.datasets.empty()) { ImGui::TextDisabled("No dataset registry loaded."); } else { std::uint64_t total_size = 0; for (const auto& dataset : dataset_registry.datasets) { total_size += dataset.size_bytes; } double total_mb = static_cast(total_size) / (1024.0 * 1024.0); ImGui::Text("Datasets: %zu", dataset_registry.datasets.size()); ImGui::SameLine(); ImGui::TextDisabled("Total size: %.2f MB", total_mb); if (!dataset_registry.generated_at.empty()) { ImGui::TextDisabled("Generated at: %s", dataset_registry.generated_at.c_str()); } if (ImGui::BeginTable("DatasetRegistryTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Dataset", ImGuiTableColumnFlags_WidthStretch, 1.4f); ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Size (MB)", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Updated", ImGuiTableColumnFlags_WidthStretch, 1.1f); ImGui::TableHeadersRow(); for (size_t i = 0; i < dataset_registry.datasets.size(); ++i) { const auto& dataset = dataset_registry.datasets[i]; if (!ContainsInsensitive(dataset.name, state.dataset_filter.data())) { continue; } ImGui::TableNextRow(); ImGui::TableNextColumn(); bool selected = static_cast(i) == state.selected_dataset_index; if (ImGui::Selectable(dataset.name.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) { state.selected_dataset_index = static_cast(i); } if (ImGui::IsItemHovered() && !dataset.path.empty()) { ImGui::BeginTooltip(); ImGui::Text("%s", dataset.path.c_str()); ImGui::EndTooltip(); } ImGui::TableNextColumn(); ImGui::Text("%zu", dataset.files.size()); ImGui::TableNextColumn(); double size_mb = static_cast(dataset.size_bytes) / (1024.0 * 1024.0); ImGui::Text("%.2f", size_mb); ImGui::TableNextColumn(); ImGui::Text("%s", dataset.updated_at.empty() ? "-" : dataset.updated_at.c_str()); } ImGui::EndTable(); } ImGui::Separator(); if (state.selected_dataset_index >= 0 && state.selected_dataset_index < static_cast(dataset_registry.datasets.size())) { const auto& selected = dataset_registry.datasets[state.selected_dataset_index]; ImGui::TextDisabled("Selected Dataset"); ImGui::Text("%s", selected.name.c_str()); ImGui::TextDisabled("%s", selected.path.empty() ? "-" : selected.path.c_str()); ImGui::Text("Files: %zu", selected.files.size()); ImGui::BeginChild("DatasetFiles", ImVec2(0, 120), true); size_t max_files = 12; for (size_t i = 0; i < selected.files.size() && i < max_files; ++i) { ImGui::BulletText("%s", selected.files[i].c_str()); } if (selected.files.size() > max_files) { ImGui::TextDisabled("... and %zu more", selected.files.size() - max_files); } ImGui::EndChild(); } else { ImGui::TextDisabled("Select a dataset to view details."); } } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Generators")) { ImGui::InputTextWithHint("##GenFilter", "Filter by generator name", state.generator_filter.data(), state.generator_filter.size()); ImGui::SameLine(); if (ImGui::Button("Clear##Gen")) state.generator_filter[0] = '\0'; if (ImGui::BeginTable("GeneratorTable", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Generator", ImGuiTableColumnFlags_WidthStretch, 1.5f); ImGui::TableSetupColumn("Accepted", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Rejected", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Rate %", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableSetupColumn("Quality", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableHeadersRow(); for (size_t i = 0; i < generators.size(); ++i) { const auto& gen = generators[i]; if (!ContainsInsensitive(gen.name, state.generator_filter.data())) continue; bool selected = static_cast(i) == state.selected_generator_index; ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::Selectable(gen.name.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) { state.selected_generator_index = static_cast(i); state.selected_generator_name = gen.name; } ImGui::TableNextColumn(); ImGui::Text("%d", gen.samples_accepted); ImGui::TableNextColumn(); ImGui::Text("%d", gen.samples_rejected); ImGui::TableNextColumn(); ImGui::Text("%.1f", gen.acceptance_rate * 100.0f); ImGui::TableNextColumn(); ImGui::Text("%.3f", gen.avg_quality); } ImGui::EndTable(); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Domains")) { if (coverage.domain_coverage.empty()) { ImGui::TextDisabled("No domain coverage data available."); } else { if (ImGui::BeginTable("DomainCoverageTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Domain", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Coverage %", ImGuiTableColumnFlags_WidthFixed, 110); ImGui::TableSetupColumn("Bar", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); for (const auto& [domain, value] : coverage.domain_coverage) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", domain.c_str()); ImGui::TableNextColumn(); ImGui::Text("%.1f", value * 100.0f); ImGui::TableNextColumn(); ImGui::ProgressBar(value, ImVec2(-1, 0)); } ImGui::EndTable(); } } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Curated Hacks")) { const auto& hacks = loader.GetCuratedHacks(); const auto& curated_error = loader.GetCuratedHacksError(); static std::unordered_map override_edits; static std::unordered_map note_buffers; static bool overrides_loaded = false; static std::string overrides_error; static std::string overrides_status; static std::string selected_hack_name; static std::array notes_buffer{}; static std::array include_buffer{}; static std::array exclude_buffer{}; auto scawful_root = ResolveHafsScawfulRoot(); std::filesystem::path override_path = scawful_root.empty() ? studio::core::FileSystem::ResolvePath("~/.config/afs/curated_hacks_overrides.toml") : scawful_root / "config" / "curated_hacks_overrides.toml"; if (!overrides_loaded) { LoadOverrideFile(override_path, &override_edits, &overrides_error); overrides_loaded = true; } if (!curated_error.empty()) { ImGui::TextColored(ImVec4(0.9f, 0.5f, 0.2f, 1.0f), "%s", curated_error.c_str()); } ImGui::Spacing(); if (ImGui::Button("Reload Overrides")) { LoadOverrideFile(override_path, &override_edits, &overrides_error); overrides_status = overrides_error.empty() ? "Overrides reloaded" : overrides_error; note_buffers.clear(); } ImGui::SameLine(); if (ImGui::Button("Save Overrides")) { std::ostringstream output; output << "# Curated hack overrides (generated by AFS Studio)\n"; int saved = 0; for (const auto& hack : hacks) { auto it = override_edits.find(hack.name); if (it == override_edits.end() || !it->second.enabled) continue; const auto& edit = it->second; output << "\n[[hack]]\n"; output << "name = \"" << hack.name << "\"\n"; if (!edit.review_status.empty()) { output << "review_status = \"" << edit.review_status << "\"\n"; } output << "weight = " << std::fixed << std::setprecision(2) << edit.weight << "\n"; if (edit.notes_set) { output << "notes = \"" << EscapeTomlString(edit.notes) << "\"\n"; } if (edit.include_set) { WriteStringArray(output, "include_globs", edit.include_globs); } if (edit.exclude_set) { WriteStringArray(output, "exclude_globs", edit.exclude_globs); } saved++; } if (studio::core::FileSystem::EnsureDirectory(override_path.parent_path()) && studio::core::FileSystem::WriteFile(override_path, output.str())) { overrides_status = "Saved " + std::to_string(saved) + " overrides"; for (auto& [name, edit] : override_edits) { edit.dirty = false; } } else { overrides_status = "Failed to save overrides"; } } ImGui::SameLine(); if (ImGui::Button("Save + Rebuild Summary")) { std::ostringstream output; output << "# Curated hack overrides (generated by AFS Studio)\n"; int saved = 0; for (const auto& hack : hacks) { auto it = override_edits.find(hack.name); if (it == override_edits.end() || !it->second.enabled) continue; const auto& edit = it->second; output << "\n[[hack]]\n"; output << "name = \"" << hack.name << "\"\n"; if (!edit.review_status.empty()) { output << "review_status = \"" << edit.review_status << "\"\n"; } output << "weight = " << std::fixed << std::setprecision(2) << edit.weight << "\n"; if (edit.notes_set) { output << "notes = \"" << EscapeTomlString(edit.notes) << "\"\n"; } if (edit.include_set) { WriteStringArray(output, "include_globs", edit.include_globs); } if (edit.exclude_set) { WriteStringArray(output, "exclude_globs", edit.exclude_globs); } saved++; } if (studio::core::FileSystem::EnsureDirectory(override_path.parent_path()) && studio::core::FileSystem::WriteFile(override_path, output.str())) { std::filesystem::path script_path = override_path.parent_path().parent_path(); script_path /= "scripts/build_curated_hacks_summary.py"; std::string build_output; bool ok = RunPythonScript(script_path, scawful_root, &build_output); overrides_status = ok ? "Saved overrides and rebuilt summary" : "Saved overrides, rebuild failed"; if (!build_output.empty()) { overrides_status += " (see logs)"; } state.should_refresh = true; for (auto& [name, edit] : override_edits) { edit.dirty = false; } } else { overrides_status = "Failed to save overrides"; } } if (!overrides_status.empty()) { ImGui::SameLine(); ImGui::TextDisabled("%s", overrides_status.c_str()); } ImGui::TextDisabled("Tip: Save + Rebuild Summary refreshes file counts automatically."); if (!overrides_error.empty()) { ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f), "%s", overrides_error.c_str()); } if (hacks.empty()) { ImGui::TextDisabled("No curated hack summary available."); if (!scawful_root.empty()) { ImGui::TextDisabled("Run: %s", (scawful_root / "scripts" / "build_curated_hacks_summary.py").string().c_str()); } else { ImGui::TextDisabled("Curated hacks plugin not configured."); } } else if (ImGui::BeginTable("CuratedHacksTable", 10, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoSavedSettings)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 1.2f); ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 85); ImGui::TableSetupColumn("Review", ImGuiTableColumnFlags_WidthFixed, 85); ImGui::TableSetupColumn("Weight", ImGuiTableColumnFlags_WidthFixed, 65); ImGui::TableSetupColumn("Eligible", ImGuiTableColumnFlags_WidthFixed, 65); ImGui::TableSetupColumn("Select", ImGuiTableColumnFlags_WidthFixed, 65); ImGui::TableSetupColumn("Org%", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Addr%", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Comm%", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch, 2.0f); ImGui::TableHeadersRow(); auto load_editor_buffers = [&](const auto& hack, HackOverrideEdit& edit) { const std::string notes_text = edit.notes_set ? edit.notes : hack.notes; const auto& include_list = edit.include_set ? edit.include_globs : hack.include_globs; const auto& exclude_list = edit.exclude_set ? edit.exclude_globs : hack.exclude_globs; FillBuffer(notes_buffer, notes_text); FillBuffer(include_buffer, JoinLines(include_list)); FillBuffer(exclude_buffer, JoinLines(exclude_list)); auto& note_state = note_buffers[hack.name]; FillBuffer(note_state.buffer, notes_text); note_state.initialized = true; }; for (const auto& hack : hacks) { auto& edit = override_edits[hack.name]; if (!edit.initialized) { edit.review_status = hack.review_status; edit.weight = hack.weight; edit.weight_set = true; edit.notes = hack.notes; edit.notes_set = !hack.notes.empty(); edit.include_globs = hack.include_globs; edit.include_set = !hack.include_globs.empty(); edit.exclude_globs = hack.exclude_globs; edit.exclude_set = !hack.exclude_globs.empty(); edit.initialized = true; } else if (!edit.weight_set) { edit.weight = hack.weight; edit.weight_set = true; } auto& note_state = note_buffers[hack.name]; if (!note_state.initialized) { const std::string notes_text = edit.notes_set ? edit.notes : hack.notes; FillBuffer(note_state.buffer, notes_text); note_state.initialized = true; } ImGui::TableNextRow(); ImGui::TableNextColumn(); bool is_selected = (selected_hack_name == hack.name); if (ImGui::Selectable(hack.name.empty() ? "-" : hack.name.c_str(), is_selected)) { selected_hack_name = hack.name; load_editor_buffers(hack, edit); } if (!hack.authors.empty() && ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Authors:"); for (const auto& author : hack.authors) { ImGui::BulletText("%s", author.c_str()); } const auto& include_list = edit.include_set ? edit.include_globs : hack.include_globs; const auto& exclude_list = edit.exclude_set ? edit.exclude_globs : hack.exclude_globs; const auto& sample_list = hack.sample_files; if (!sample_list.empty()) { ImGui::Separator(); ImGui::Text("Sample files:"); for (const auto& sample : sample_list) { ImGui::BulletText("%s", sample.c_str()); } } if (!include_list.empty()) { ImGui::Separator(); ImGui::Text("Include globs:"); for (const auto& glob : include_list) { ImGui::BulletText("%s", glob.c_str()); } } if (!exclude_list.empty()) { ImGui::Separator(); ImGui::Text("Exclude globs:"); for (const auto& glob : exclude_list) { ImGui::BulletText("%s", glob.c_str()); } } ImGui::EndTooltip(); } ImGui::TableNextColumn(); if (ImGui::Button(("Edit##" + hack.name).c_str())) { selected_hack_name = hack.name; load_editor_buffers(hack, edit); } ImGui::TableNextColumn(); bool enabled = edit.enabled; if (ImGui::Checkbox(("##override_" + hack.name).c_str(), &enabled)) { edit.enabled = enabled; edit.dirty = true; } ImGui::TableNextColumn(); if (edit.dirty) { ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f), "dirty"); } else { ImGui::TextDisabled("-"); } ImGui::TableNextColumn(); ImGui::BeginDisabled(!edit.enabled); float weight = edit.weight; ImGui::SetNextItemWidth(-1.0f); if (ImGui::SliderFloat(("##weight_" + hack.name).c_str(), &weight, 0.0f, 1.0f, "%.2f")) { edit.weight = weight; edit.weight_set = true; edit.dirty = true; } ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::BeginDisabled(!edit.enabled); const char* status_items[] = {"", "approved", "hold", "rejected"}; int status_index = 0; if (edit.review_status == "approved") status_index = 1; else if (edit.review_status == "hold") status_index = 2; else if (edit.review_status == "rejected") status_index = 3; ImGui::SetNextItemWidth(-1.0f); if (ImGui::Combo(("##review_" + hack.name).c_str(), &status_index, status_items, 4)) { edit.review_status = status_items[status_index]; edit.dirty = true; } ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::Text("%d/%d", hack.selected_files, hack.eligible_files); ImGui::TableNextColumn(); ImGui::Text("%.0f", hack.org_ratio * 100.0f); ImGui::TableNextColumn(); ImGui::Text("%.0f", hack.address_ratio * 100.0f); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(-1.0f); if (ImGui::InputTextWithHint(("##note_" + hack.name).c_str(), "Notes...", note_state.buffer.data(), note_state.buffer.size())) { edit.notes = TrimCopy(std::string(note_state.buffer.data())); edit.notes_set = true; edit.enabled = true; edit.dirty = true; if (selected_hack_name == hack.name) { FillBuffer(notes_buffer, edit.notes); } } std::string notes = edit.notes_set ? edit.notes : hack.notes; if (notes.empty()) notes = "-"; if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextWrapped("%s", notes.c_str()); if (!hack.path.empty()) { ImGui::Separator(); ImGui::TextDisabled("%s", hack.path.c_str()); } ImGui::EndTooltip(); } } ImGui::EndTable(); } if (!hacks.empty()) { ImGui::Spacing(); ImGui::Separator(); ImGui::Text(ICON_MD_EDIT " Override Editor"); if (selected_hack_name.empty()) { ImGui::TextDisabled("Select a hack row to edit include/exclude globs and notes."); } else { auto it = std::find_if(hacks.begin(), hacks.end(), [&](const auto& h) { return h.name == selected_hack_name; }); if (it == hacks.end()) { ImGui::TextDisabled("Selected hack not found."); } else { auto& edit = override_edits[it->name]; ImGui::Text("Editing: %s", it->name.c_str()); ImGui::SameLine(); if (ImGui::Button("Revert Override")) { edit.enabled = false; edit.dirty = true; edit.review_status = it->review_status; edit.weight = it->weight; edit.weight_set = false; edit.notes = it->notes; edit.notes_set = false; edit.include_globs = it->include_globs; edit.include_set = false; edit.exclude_globs = it->exclude_globs; edit.exclude_set = false; FillBuffer(notes_buffer, it->notes); FillBuffer(include_buffer, JoinLines(it->include_globs)); FillBuffer(exclude_buffer, JoinLines(it->exclude_globs)); overrides_status = "Cleared override for " + it->name + " (not saved)"; } bool notes_changed = ImGui::InputTextMultiline( "Notes", notes_buffer.data(), notes_buffer.size(), ImVec2(-1, 90)); bool include_changed = ImGui::InputTextMultiline( "Include globs (one per line)", include_buffer.data(), include_buffer.size(), ImVec2(-1, 70)); bool exclude_changed = ImGui::InputTextMultiline( "Exclude globs (one per line)", exclude_buffer.data(), exclude_buffer.size(), ImVec2(-1, 70)); if (notes_changed || include_changed || exclude_changed) { edit.notes = TrimCopy(std::string(notes_buffer.data())); edit.notes_set = true; edit.include_globs = SplitLines(include_buffer.data()); edit.include_set = true; edit.exclude_globs = SplitLines(exclude_buffer.data()); edit.exclude_set = true; edit.enabled = true; edit.dirty = true; overrides_status = "Updated overrides for " + it->name + " (not saved)"; } if (ImGui::Button("Reset Editor")) { auto load_editor_buffers = [&](const auto& hack, HackOverrideEdit& edit_ref) { const std::string notes_text = edit_ref.notes_set ? edit_ref.notes : hack.notes; const auto& include_list = edit_ref.include_set ? edit_ref.include_globs : hack.include_globs; const auto& exclude_list = edit_ref.exclude_set ? edit_ref.exclude_globs : hack.exclude_globs; FillBuffer(notes_buffer, notes_text); FillBuffer(include_buffer, JoinLines(include_list)); FillBuffer(exclude_buffer, JoinLines(exclude_list)); }; load_editor_buffers(*it, edit); } } } } ImGui::EndTabItem(); } ImGui::EndTabBar(); } } void RenderSystemsPanel(AppState& state, ImFont* font_header, std::function refresh_callback) { if (font_header) ImGui::PushFont(font_header); ImGui::Text(ICON_MD_ROUTER " SYSTEMS OVERVIEW"); if (font_header) ImGui::PopFont(); ImGui::Separator(); if (ImGui::Button(ICON_MD_REFRESH " Refresh Now", ImVec2(-1, 0))) { if (refresh_callback) refresh_callback("ui"); } ImGui::Spacing(); double seconds_since = std::max(0.0, glfwGetTime() - state.last_refresh_time); ImGui::Text("Auto Refresh: %s", state.auto_refresh ? "On" : "Off"); ImGui::Text("Interval: %.1fs", state.refresh_interval_sec); ImGui::Text("Last Refresh: %.0fs ago", seconds_since); ImGui::Text("Simulation: %s", state.simulate_activity ? "On" : "Off"); ImGui::Text("Quality Threshold: %.2f", state.quality_threshold); ImGui::Text("Mission Concurrency: %d", state.mission_concurrency); ImGui::Spacing(); ImGui::Separator(); ImGui::TextDisabled("Swarm Snapshot"); int active_count = 0; int total_queue = 0; float total_success = 0.0f; for (const auto& agent : state.agents) { if (agent.enabled) active_count++; total_queue += agent.queue_depth; total_success += agent.success_rate; } if (!state.agents.empty()) total_success /= static_cast(state.agents.size()); if (ImGui::BeginTable("SystemSnapshot", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableNextColumn(); ImGui::Text("Active Agents"); ImGui::Text("Queue Depth"); ImGui::Text("Avg Success"); ImGui::TableNextColumn(); ImGui::Text("%d / %d", active_count, static_cast(state.agents.size())); ImGui::Text("%d", total_queue); ImGui::Text("%.1f%%", total_success * 100.0f); ImGui::EndTable(); } ImGui::Spacing(); ImGui::Separator(); ImGui::TextDisabled("Core Services"); auto render_row = [](const char* name, const char* status) { ImVec4 status_color = ImVec4(0.4f, 0.8f, 0.4f, 1.0f); if (strcmp(status, "Warning") == 0) status_color = ImVec4(0.9f, 0.7f, 0.2f, 1.0f); else if (strcmp(status, "Error") == 0) status_color = ImVec4(0.9f, 0.3f, 0.3f, 1.0f); ImGui::Bullet(); ImGui::SameLine(); ImGui::Text("%s", name); ImGui::SameLine(); ImGui::TextColored(status_color, "%s", status); }; render_row("Orchestrator", "Active"); render_row("Knowledge Base", "Active"); render_row("Trainer", "Active"); render_row("Embedding SVC", "Warning"); } void RenderMenuBar(AppState& state, std::function refresh_callback, std::function quit_callback, ShortcutManager& shortcuts, bool* show_sample_review, bool* show_shortcuts_window) { if (ImGui::BeginMainMenuBar()) { auto shortcut_label = [&](ActionId action_id) -> std::string { return shortcuts.FormatShortcut(action_id, ImGui::GetIO()); }; if (ImGui::BeginMenu("File")) { std::string refresh_shortcut = shortcut_label(ActionId::Refresh); if (ImGui::MenuItem("Refresh", refresh_shortcut.empty() ? nullptr : refresh_shortcut.c_str())) { if (refresh_callback) refresh_callback("manual"); } ImGui::Separator(); std::string quit_shortcut = shortcut_label(ActionId::Quit); if (ImGui::MenuItem("Quit", quit_shortcut.empty() ? nullptr : quit_shortcut.c_str())) { if (quit_callback) quit_callback(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("View")) { if (ImGui::MenuItem("Reset Layout (F1)")) { state.force_reset_layout = true; } ImGui::Separator(); if (ImGui::BeginMenu("Workspace")) { std::string dash_shortcut = shortcut_label(ActionId::WorkspaceDashboard); if (ImGui::MenuItem("Dashboard", dash_shortcut.empty() ? nullptr : dash_shortcut.c_str(), state.current_workspace == Workspace::Dashboard)) { state.current_workspace = Workspace::Dashboard; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string analysis_shortcut = shortcut_label(ActionId::WorkspaceAnalysis); if (ImGui::MenuItem("Analysis", analysis_shortcut.empty() ? nullptr : analysis_shortcut.c_str(), state.current_workspace == Workspace::Analysis)) { state.current_workspace = Workspace::Analysis; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string optimization_shortcut = shortcut_label(ActionId::WorkspaceOptimization); if (ImGui::MenuItem("Optimization", optimization_shortcut.empty() ? nullptr : optimization_shortcut.c_str(), state.current_workspace == Workspace::Optimization)) { state.current_workspace = Workspace::Optimization; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string systems_shortcut = shortcut_label(ActionId::WorkspaceSystems); if (ImGui::MenuItem("Systems", systems_shortcut.empty() ? nullptr : systems_shortcut.c_str(), state.current_workspace == Workspace::Systems)) { state.current_workspace = Workspace::Systems; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string custom_shortcut = shortcut_label(ActionId::WorkspaceCustom); if (ImGui::MenuItem("Custom Grid", custom_shortcut.empty() ? nullptr : custom_shortcut.c_str(), state.current_workspace == Workspace::Custom)) { state.current_workspace = Workspace::Custom; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string training_shortcut = shortcut_label(ActionId::WorkspaceTraining); if (ImGui::MenuItem("Training Hub", training_shortcut.empty() ? nullptr : training_shortcut.c_str(), state.current_workspace == Workspace::Training)) { state.current_workspace = Workspace::Training; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } std::string context_shortcut = shortcut_label(ActionId::WorkspaceContext); if (ImGui::MenuItem("Context Broker", context_shortcut.empty() ? nullptr : context_shortcut.c_str(), state.current_workspace == Workspace::Context)) { state.current_workspace = Workspace::Context; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Theme Profile")) { if (ImGui::MenuItem("Deep Sea (Default)", nullptr, state.current_theme == ThemeProfile::Default)) state.current_theme = ThemeProfile::Default; if (ImGui::MenuItem("Cyberpunk", nullptr, state.current_theme == ThemeProfile::Cyberpunk)) state.current_theme = ThemeProfile::Cyberpunk; if (ImGui::MenuItem("Monochrome", nullptr, state.current_theme == ThemeProfile::Monochrome)) state.current_theme = ThemeProfile::Monochrome; if (ImGui::MenuItem("Solarized", nullptr, state.current_theme == ThemeProfile::Solarized)) state.current_theme = ThemeProfile::Solarized; if (ImGui::MenuItem("Nord", nullptr, state.current_theme == ThemeProfile::Nord)) state.current_theme = ThemeProfile::Nord; if (ImGui::MenuItem("Dracula", nullptr, state.current_theme == ThemeProfile::Dracula)) state.current_theme = ThemeProfile::Dracula; ImGui::EndMenu(); } ImGui::Separator(); if (ImGui::BeginMenu("Panels")) { std::string inspector_shortcut = shortcut_label(ActionId::ToggleInspector); ImGui::MenuItem("Inspector", inspector_shortcut.empty() ? nullptr : inspector_shortcut.c_str(), &state.show_inspector); std::string dataset_shortcut = shortcut_label(ActionId::ToggleDatasetPanel); ImGui::MenuItem("Dataset Panel", dataset_shortcut.empty() ? nullptr : dataset_shortcut.c_str(), &state.show_dataset_panel); std::string systems_panel_shortcut = shortcut_label(ActionId::ToggleSystemsPanel); ImGui::MenuItem("Systems Panel", systems_panel_shortcut.empty() ? nullptr : systems_panel_shortcut.c_str(), &state.show_systems_panel); std::string status_shortcut = shortcut_label(ActionId::ToggleStatusBar); ImGui::MenuItem("Status Strip", status_shortcut.empty() ? nullptr : status_shortcut.c_str(), &state.show_status_strip); std::string controls_shortcut = shortcut_label(ActionId::ToggleControls); ImGui::MenuItem("Sidebar Controls", controls_shortcut.empty() ? nullptr : controls_shortcut.c_str(), &state.show_controls); ImGui::MenuItem("Chat Panel", nullptr, &state.show_chat_panel); ImGui::Separator(); ImGui::MenuItem("Quality Trends", nullptr, &state.show_quality_trends); ImGui::MenuItem("Generator Efficiency", nullptr, &state.show_generator_efficiency); ImGui::MenuItem("Coverage Density", nullptr, &state.show_coverage_density); ImGui::EndMenu(); } if (ImGui::BeginMenu("Windows")) { if (show_sample_review) { std::string review_shortcut = shortcut_label(ActionId::ToggleSampleReview); ImGui::MenuItem("Sample Review", review_shortcut.empty() ? nullptr : review_shortcut.c_str(), show_sample_review); } if (show_shortcuts_window) { std::string shortcuts_shortcut = shortcut_label(ActionId::ToggleShortcutsWindow); ImGui::MenuItem("Keyboard Shortcuts", shortcuts_shortcut.empty() ? nullptr : shortcuts_shortcut.c_str(), show_shortcuts_window); } ImGui::EndMenu(); } ImGui::Separator(); std::string compact_shortcut = shortcut_label(ActionId::ToggleCompactUI); ImGui::MenuItem("Compact UI", compact_shortcut.empty() ? nullptr : compact_shortcut.c_str(), &state.compact_charts); std::string lock_shortcut = shortcut_label(ActionId::ToggleLockLayout); ImGui::MenuItem("Lock Layout", lock_shortcut.empty() ? nullptr : lock_shortcut.c_str(), &state.lock_layout); std::string reset_shortcut = shortcut_label(ActionId::ResetLayout); if (ImGui::MenuItem("Reset Layout", reset_shortcut.empty() ? nullptr : reset_shortcut.c_str())) { state.force_reset_layout = true; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Tools")) { std::string auto_refresh_shortcut = shortcut_label(ActionId::ToggleAutoRefresh); ImGui::MenuItem("Auto Refresh", auto_refresh_shortcut.empty() ? nullptr : auto_refresh_shortcut.c_str(), &state.auto_refresh); std::string simulate_shortcut = shortcut_label(ActionId::ToggleSimulation); ImGui::MenuItem("Simulate Activity", simulate_shortcut.empty() ? nullptr : simulate_shortcut.c_str(), &state.simulate_activity); ImGui::Separator(); std::string demo_shortcut = shortcut_label(ActionId::ToggleDemoWindow); ImGui::MenuItem("Show ImGui Demo", demo_shortcut.empty() ? nullptr : demo_shortcut.c_str(), &state.show_demo_window); ImGui::EndMenu(); } if (ImGui::BeginMenu("Help")) { if (ImGui::MenuItem("Documentation")) {} std::string help_shortcut = shortcut_label(ActionId::ToggleShortcutsWindow); if (ImGui::MenuItem("Shortcuts", help_shortcut.empty() ? nullptr : help_shortcut.c_str())) { if (show_shortcuts_window) *show_shortcuts_window = true; } ImGui::Separator(); if (ImGui::MenuItem("About AFS Viz")) {} ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } } void RenderSidebar(AppState& state, const DataLoader& loader, ImFont* font_ui, ImFont* font_header) { // Make the entire sidebar content scrollable to avoid overlaps ImGui::BeginChild("SidebarScroll", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NoBackground); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 4)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.05f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.08f)); auto sidebar_button = [&](const char* label, Workspace ws, const char* icon) { bool active = state.current_workspace == ws; ImGui::PushID(label); ImVec2 size = ImVec2(ImGui::GetContentRegionAvail().x, 38); ImVec2 p_cursor = ImGui::GetCursorScreenPos(); if (ImGui::InvisibleButton("##btn", size)) { if (state.current_workspace != ws) { state.current_workspace = ws; if (state.reset_layout_on_workspace_change) state.force_reset_layout = true; } } bool hovered = ImGui::IsItemHovered(); bool pressed = ImGui::IsItemActive(); ImDrawList* draw = ImGui::GetWindowDrawList(); ImU32 bg_col = hovered ? ImGui::GetColorU32(ImGuiCol_ButtonHovered) : (pressed ? ImGui::GetColorU32(ImGuiCol_ButtonActive) : 0); if (active) bg_col = ImGui::GetColorU32(ImVec4(1, 1, 1, 0.08f)); if (bg_col != 0) { draw->AddRectFilled(p_cursor, ImVec2(p_cursor.x + size.x, p_cursor.y + size.y), bg_col, 4.0f); } if (active) { draw->AddRectFilled(p_cursor, ImVec2(p_cursor.x + 3, p_cursor.y + size.y), ImGui::GetColorU32(ImVec4(0.40f, 0.85f, 1.0f, 1.0f)), 2.0f); } ImVec2 text_pos = ImVec2(p_cursor.x + 12, p_cursor.y + (size.y - ImGui::GetFontSize()) * 0.5f); draw->AddText(font_ui, ImGui::GetFontSize(), text_pos, active ? ImGui::GetColorU32(ImVec4(0.40f, 0.85f, 1.0f, 1.0f)) : ImGui::GetColorU32(ImGuiCol_Text), std::string(std::string(icon) + " " + label).c_str()); if (hovered) ImGui::SetTooltip("%s workspace", label); ImGui::PopID(); }; auto sidebar_header = [&](const char* title, const char* icon = nullptr) { ImGui::Spacing(); ImGui::Spacing(); if (font_header) ImGui::PushFont(font_header); ImGui::SetCursorPosX(12); if (icon) { ImGui::TextDisabled("%s %s", icon, title); } else { ImGui::TextDisabled("%s", title); } if (font_header) ImGui::PopFont(); ImGui::Spacing(); }; sidebar_header("WORKSPACES", ICON_MD_VIEW_QUILT); sidebar_button("Dashboard", Workspace::Dashboard, ICON_MD_DASHBOARD); sidebar_button("Analysis", Workspace::Analysis, ICON_MD_ANALYTICS); sidebar_button("Optimization", Workspace::Optimization, ICON_MD_SETTINGS_INPUT_COMPONENT); sidebar_header("OPERATIONS", ICON_MD_SETTINGS_SUGGEST); sidebar_button("Systems", Workspace::Systems, ICON_MD_ROUTER); sidebar_button("Training", Workspace::Training, ICON_MD_MODEL_TRAINING); sidebar_button("Custom Grid", Workspace::Custom, ICON_MD_DASHBOARD_CUSTOMIZE); sidebar_header("REGISTRIES", ICON_MD_STORAGE); sidebar_button("Context", Workspace::Context, ICON_MD_FOLDER_OPEN); sidebar_button("Models", Workspace::Models, ICON_MD_STICKY_NOTE_2); // New: Useful Tools Section sidebar_header("SYSTEM TOOLS", ICON_MD_HANDYMAN); ImGui::Indent(12); // Health Summary { float health = 0.92f; // Mock health ImGui::TextDisabled("System Health"); ImGui::ProgressBar(health, ImVec2(-12, 4), ""); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Overall reliability score: %.0f%%", health * 100.0f); } ImGui::Spacing(); // Quick Toggles if (ImGui::Checkbox("Simulate", &state.simulate_activity)) {} if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle background task simulation"); if (ImGui::Checkbox("Auto Refresh", &state.auto_refresh)) {} if (ImGui::IsItemHovered()) ImGui::SetTooltip("Refresh data every %.1fs", state.refresh_interval_sec); ImGui::Unindent(12); // New: Mounts Management Section sidebar_header("LOCAL MOUNTS", ICON_MD_STORAGE); ImGui::Indent(12); const auto& mounts = loader.GetMounts(); if (mounts.empty()) { ImGui::TextDisabled("No mounts discovered"); } else { for (const auto& mount : mounts) { ImGui::BeginGroup(); ImGui::TextColored(mount.active ? ImVec4(0.4f, 1.0f, 0.6f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f), mount.active ? ICON_MD_DNS : ICON_MD_DASHBOARD_CUSTOMIZE); ImGui::SameLine(); ImGui::Text("%s", mount.name.c_str()); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Path: %s\nStatus: %s", mount.path.c_str(), mount.active ? "Connected" : "Disconnected"); ImGui::EndGroup(); } } if (ImGui::SmallButton(ICON_MD_STORAGE " Mount Drives")) { const_cast(loader).MountDrive("all"); } ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_ADD " Add Mount")) { // TODO: Implement mount dialog } ImGui::Unindent(12); ImGui::PopStyleColor(3); ImGui::PopStyleVar(); ImGui::EndChild(); // End SidebarScroll } } // namespace ui } // namespace viz } // namespace afs