diff --git a/apps/studio/src/app.cc b/apps/studio/src/app.cc index ab1b4d0..ba98dd4 100644 --- a/apps/studio/src/app.cc +++ b/apps/studio/src/app.cc @@ -113,6 +113,7 @@ void App::RefreshData(const char* reason) { bool ok = loader_.Refresh(); state_.last_refresh_time = glfwGetTime(); SyncDataBackedState(); + EnsureActiveGraph(); const auto& status = loader_.GetLastStatus(); std::string msg; @@ -230,6 +231,35 @@ void App::SyncDataBackedState() { } } +void App::EnsureActiveGraph() { + if (state_.active_graph != PlotKind::None && + graph_browser_.IsGraphAvailable(state_.active_graph, state_, loader_)) { + return; + } + + const PlotKind preferred[] = { + PlotKind::KnowledgeGraph, + PlotKind::DatasetInventory, + PlotKind::MountsStatus, + }; + + for (auto kind : preferred) { + if (graph_browser_.IsGraphAvailable(kind, state_, loader_)) { + graph_navigator_.NavigateToGraph(state_, kind); + return; + } + } + + for (const auto& graph : graph_browser_.GetAllGraphs()) { + if (graph_browser_.IsGraphAvailable(graph.kind, state_, loader_)) { + graph_navigator_.NavigateToGraph(state_, graph.kind); + return; + } + } + + state_.active_graph = PlotKind::None; +} + void App::SeedDefaultState() { ui::AppendLog(state_, "system", "AFS Studio environment ready.", "system"); state_.sparkline_data.resize(30, 0.0f); @@ -367,7 +397,7 @@ void App::RenderLayout() { if (state_.show_graph_browser) { ImGui::Begin("GraphBrowser", &state_.show_graph_browser, ImGuiWindowFlags_NoCollapse); - graph_browser_.Render(state_); + graph_browser_.Render(state_, loader_); ImGui::End(); } diff --git a/apps/studio/src/app.h b/apps/studio/src/app.h index 7ae0bba..8860bf0 100644 --- a/apps/studio/src/app.h +++ b/apps/studio/src/app.h @@ -38,10 +38,11 @@ class App { const DataLoader& loader() const { return loader_; } private: - void RefreshData(const char* reason); - void SeedDefaultState(); - void SyncDataBackedState(); - void TickSimulatedMetrics(float dt); + void RefreshData(const char* reason); + void SeedDefaultState(); + void SyncDataBackedState(); + void TickSimulatedMetrics(float dt); + void EnsureActiveGraph(); void RenderFrame(); void RenderLayout(); diff --git a/apps/studio/src/core/registry_reader.cc b/apps/studio/src/core/registry_reader.cc index ee43368..2255d5a 100644 --- a/apps/studio/src/core/registry_reader.cc +++ b/apps/studio/src/core/registry_reader.cc @@ -40,7 +40,17 @@ RegistryReader::RegistryReader(const std::filesystem::path& registry_path) : registry_path_(registry_path) {} std::filesystem::path RegistryReader::ResolveDefaultPath() const { - return ResolveContextRoot() / "models" / "registry.json"; + auto preferred = ResolveContextRoot() / "models" / "registry.json"; + if (core::FileSystem::Exists(preferred)) { + return preferred; + } + + auto fallback = core::FileSystem::ResolvePath("~/.context/models/registry.json"); + if (core::FileSystem::Exists(fallback)) { + return fallback; + } + + return preferred; } bool RegistryReader::Exists() const { diff --git a/apps/studio/src/data_loader.cc b/apps/studio/src/data_loader.cc index e583eeb..9cb8a1b 100644 --- a/apps/studio/src/data_loader.cc +++ b/apps/studio/src/data_loader.cc @@ -22,6 +22,8 @@ using json = nlohmann::json; constexpr size_t kTrendWindow = 5; constexpr float kPi = 3.14159265f; +std::optional ResolveTrunkRoot(); + std::optional ResolveHafsScawfulRoot() { const char* env_root = std::getenv("AFS_SCAWFUL_ROOT"); if (env_root && env_root[0] != '\0') { @@ -318,6 +320,15 @@ bool DataLoader::Refresh() { } LoadResult resource = LoadResourceIndex(&next_resource_index); + last_status_.resource_index_found = resource.found; + last_status_.resource_index_ok = resource.ok; + if (resource.found && !resource.ok) { + last_status_.error_count += 1; + if (last_status_.last_error.empty()) { + last_status_.last_error = resource.error; + last_status_.last_error_source = "resource_index.json"; + } + } if (!resource.found) { resource_index_ = ResourceIndexData{}; resource_index_error_ = "resource_index.json not found"; @@ -329,6 +340,15 @@ bool DataLoader::Refresh() { } LoadResult registry = LoadDatasetRegistry(&next_dataset_registry); + last_status_.dataset_registry_found = registry.found; + last_status_.dataset_registry_ok = registry.ok; + if (registry.found && !registry.ok) { + last_status_.error_count += 1; + if (last_status_.last_error.empty()) { + last_status_.last_error = registry.error; + last_status_.last_error_source = "dataset_registry.json"; + } + } if (!registry.found) { dataset_registry_ = DatasetRegistryData{}; dataset_registry_error_ = "dataset_registry.json not found"; @@ -340,6 +360,15 @@ bool DataLoader::Refresh() { } LoadResult context_graph = LoadContextGraph(&next_context_graph); + last_status_.context_graph_found = context_graph.found; + last_status_.context_graph_ok = context_graph.ok; + if (context_graph.found && !context_graph.ok) { + last_status_.error_count += 1; + if (last_status_.last_error.empty()) { + last_status_.last_error = context_graph.error; + last_status_.last_error_source = "afs_graph.json"; + } + } if (!context_graph.found) { context_graph_ = ContextGraphData{}; context_graph_error_ = "afs_graph.json not found"; @@ -386,7 +415,10 @@ bool DataLoader::Refresh() { has_data_ = !quality_trends_.empty() || !generator_stats_.empty() || !embedding_regions_.empty() || !training_runs_.empty() || !optimization_data_.domain_effectiveness.empty() || - !optimization_data_.threshold_sensitivity.empty(); + !optimization_data_.threshold_sensitivity.empty() || + !dataset_registry_.datasets.empty() || + resource_index_.total_files > 0 || + !context_graph_.labels.empty(); last_error_ = last_status_.last_error; return last_status_.AnyOk() || (!(last_status_.FoundCount() > 0) && has_data_); diff --git a/apps/studio/src/data_loader.h b/apps/studio/src/data_loader.h index 96b66ce..f4d7d02 100644 --- a/apps/studio/src/data_loader.h +++ b/apps/studio/src/data_loader.h @@ -150,19 +150,30 @@ struct LoadStatus { bool active_ok = false; bool training_found = false; bool training_ok = false; + bool resource_index_found = false; + bool resource_index_ok = false; + bool dataset_registry_found = false; + bool dataset_registry_ok = false; + bool context_graph_found = false; + bool context_graph_ok = false; int error_count = 0; std::string last_error; std::string last_error_source; int FoundCount() const { return static_cast(quality_found) + static_cast(active_found) + - static_cast(training_found); + static_cast(training_found) + static_cast(resource_index_found) + + static_cast(dataset_registry_found) + static_cast(context_graph_found); } int OkCount() const { return static_cast(quality_ok) + static_cast(active_ok) + - static_cast(training_ok); + static_cast(training_ok) + static_cast(resource_index_ok) + + static_cast(dataset_registry_ok) + static_cast(context_graph_ok); + } + bool AnyOk() const { + return quality_ok || active_ok || training_ok || resource_index_ok || + dataset_registry_ok || context_graph_ok; } - bool AnyOk() const { return quality_ok || active_ok || training_ok; } }; /// Loads training data from JSON files. diff --git a/apps/studio/src/models/state.h b/apps/studio/src/models/state.h index 92d7a88..9b4c0bc 100644 --- a/apps/studio/src/models/state.h +++ b/apps/studio/src/models/state.h @@ -37,6 +37,7 @@ enum class PlotKind { Effectiveness, Thresholds, MountsStatus, + DatasetInventory, }; enum class GraphViewMode { Single, Compare, Overview }; @@ -238,7 +239,7 @@ struct AppState { bool show_plot_legends = true; bool show_plot_markers = true; bool data_scientist_mode = false; - bool show_all_charts = true; + bool show_all_charts = false; float pulse_timer = 0.0f; int custom_grid_rows = 2; int custom_grid_columns = 2; diff --git a/apps/studio/src/ui/components/charts.cc b/apps/studio/src/ui/components/charts.cc index 0ac3041..ff09bbe 100644 --- a/apps/studio/src/ui/components/charts.cc +++ b/apps/studio/src/ui/components/charts.cc @@ -14,6 +14,7 @@ namespace viz { namespace ui { void RenderMountsChart(AppState& state, const DataLoader& loader); void RenderKnowledgeGraphChart(AppState& state, const DataLoader& loader); +void RenderDatasetInventoryChart(AppState& state, const DataLoader& loader); @@ -265,6 +266,65 @@ void RenderTrainingLossChart(AppState& state, const DataLoader& loader) { ImPlot::PopStyleVar(6); } +void RenderDatasetInventoryChart(AppState& state, const DataLoader& loader) { + RenderChartHeader(PlotKind::DatasetInventory, + "DATASET INVENTORY", + "Dataset sizes and file counts from the registry index.", + state); + + const auto& registry = loader.GetDatasetRegistry(); + if (registry.datasets.empty()) { + ImGui::TextDisabled("No dataset registry data available"); + return; + } + + std::vector labels; + std::vector sizes_mb; + std::vector file_counts; + std::vector label_storage; + labels.reserve(registry.datasets.size()); + sizes_mb.reserve(registry.datasets.size()); + file_counts.reserve(registry.datasets.size()); + label_storage.reserve(registry.datasets.size()); + + for (const auto& dataset : registry.datasets) { + label_storage.push_back(dataset.name); + sizes_mb.push_back(static_cast(dataset.size_bytes) / (1024.0f * 1024.0f)); + file_counts.push_back(static_cast(dataset.files.size())); + } + + for (const auto& label : label_storage) labels.push_back(label.c_str()); + + ImPlotFlags plot_flags = BasePlotFlags(state, true); + ApplyPremiumPlotStyles("##DatasetInventory", state); + if (ImPlot::BeginPlot("##DatasetInventory", ImGui::GetContentRegionAvail(), plot_flags)) { + ImPlotAxisFlags axis_flags = static_cast(GetPlotAxisFlags(state)); + ImPlot::SetupAxes("Dataset", "Size (MB)", axis_flags, axis_flags); + ImPlot::SetupAxis(ImAxis_Y2, "Files", axis_flags); + if (!labels.empty()) { + ImPlot::SetupAxisTicks(ImAxis_X1, 0, static_cast(labels.size() - 1), + static_cast(labels.size()), labels.data()); + } + HandlePlotContextMenu(PlotKind::DatasetInventory, state); + + ImPlot::SetNextFillStyle(GetSeriesColor(3), 0.75f); + ImPlot::PlotBars("Size (MB)", sizes_mb.data(), + static_cast(sizes_mb.size()), 0.6f); + + ImPlot::SetAxis(ImAxis_Y2); + ImPlot::SetNextLineStyle(GetSeriesColor(6), 2.0f); + if (state.show_plot_markers) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 4.0f, GetSeriesColor(6)); + } + ImPlot::PlotLine("Files", file_counts.data(), + static_cast(file_counts.size())); + + ImPlot::EndPlot(); + } + ImPlot::PopStyleColor(2); + ImPlot::PopStyleVar(6); +} + void RenderRejectionChart(AppState& state, const DataLoader& loader) { RenderChartHeader(PlotKind::Rejections, "REJECTION REASONS", @@ -1075,6 +1135,9 @@ void RenderPlotByKind(PlotKind kind, AppState& state, const DataLoader& loader) case PlotKind::MountsStatus: RenderMountsChart(state, loader); break; + case PlotKind::DatasetInventory: + RenderDatasetInventoryChart(state, loader); + break; default: break; } diff --git a/apps/studio/src/ui/components/graph_browser.cc b/apps/studio/src/ui/components/graph_browser.cc index 8789e19..5d81a01 100644 --- a/apps/studio/src/ui/components/graph_browser.cc +++ b/apps/studio/src/ui/components/graph_browser.cc @@ -1,6 +1,7 @@ #include "graph_browser.h" #include "../core.h" #include "../../icons.h" +#include "../../data_loader.h" #include #include @@ -15,6 +16,7 @@ void GraphBrowser::InitializeGraphRegistry() { // Training Category {PlotKind::TrainingLoss, "Training Loss", "Loss curves over training steps", GraphCategory::Training, true, true, true, true}, {PlotKind::LossVsSamples, "Loss vs Samples", "Training loss progression by sample count", GraphCategory::Training, false, true, true, false}, + {PlotKind::DatasetInventory, "Dataset Inventory", "Dataset sizes and file counts", GraphCategory::Training, false, false, true, false}, // Quality Category {PlotKind::QualityTrends, "Quality Trends", "Data quality trends by domain", GraphCategory::Quality, true, true, true, true}, @@ -93,7 +95,57 @@ const GraphInfo* GraphBrowser::GetGraphInfo(PlotKind kind) const { return nullptr; } -void GraphBrowser::Render(AppState& state) { +bool GraphBrowser::IsGraphAvailable(PlotKind kind, + const AppState& state, + const DataLoader& loader) const { + switch (kind) { + case PlotKind::QualityTrends: + case PlotKind::QualityDirection: + return !loader.GetQualityTrends().empty(); + case PlotKind::GeneratorEfficiency: + case PlotKind::GeneratorMix: + return !loader.GetGeneratorStats().empty(); + case PlotKind::CoverageDensity: + case PlotKind::EmbeddingDensity: + case PlotKind::EmbeddingQuality: + case PlotKind::LatentSpace: + return !loader.GetEmbeddingRegions().empty(); + case PlotKind::TrainingLoss: + case PlotKind::LossVsSamples: + return !loader.GetTrainingRuns().empty(); + case PlotKind::DomainCoverage: + return !loader.GetCoverage().domain_coverage.empty(); + case PlotKind::Rejections: + return !loader.GetRejectionSummary().reasons.empty(); + case PlotKind::EvalMetrics: { + const auto& runs = loader.GetTrainingRuns(); + for (const auto& run : runs) { + if (!run.eval_metrics.empty()) return true; + } + return false; + } + case PlotKind::Effectiveness: + return !loader.GetOptimizationData().domain_effectiveness.empty() || + !loader.GetCoverage().domain_coverage.empty(); + case PlotKind::Thresholds: + return !loader.GetOptimizationData().threshold_sensitivity.empty(); + case PlotKind::AgentThroughput: + return !state.agents.empty(); + case PlotKind::MissionQueue: + case PlotKind::MissionProgress: + return !state.missions.empty(); + case PlotKind::MountsStatus: + return !loader.GetMounts().empty(); + case PlotKind::KnowledgeGraph: + return !loader.GetContextGraph().labels.empty(); + case PlotKind::DatasetInventory: + return !loader.GetDatasetRegistry().datasets.empty(); + default: + return true; + } +} + +void GraphBrowser::Render(AppState& state, const DataLoader& loader) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); // Search bar @@ -136,7 +188,17 @@ void GraphBrowser::Render(AppState& state) { for (auto kind : state.graph_bookmarks) { const GraphInfo* info = GetGraphInfo(kind); if (info) { - RenderGraphItem(*info, state); + bool available = IsGraphAvailable(info->kind, state, loader); + if (!available && !state.show_all_charts) { + continue; + } + if (!available && state.show_all_charts) { + ImGui::BeginDisabled(); + RenderGraphItem(*info, state); + ImGui::EndDisabled(); + } else { + RenderGraphItem(*info, state); + } } } } @@ -158,7 +220,17 @@ void GraphBrowser::Render(AppState& state) { for (auto kind : recent_unique) { const GraphInfo* info = GetGraphInfo(kind); if (info) { - RenderGraphItem(*info, state); + bool available = IsGraphAvailable(info->kind, state, loader); + if (!available && !state.show_all_charts) { + continue; + } + if (!available && state.show_all_charts) { + ImGui::BeginDisabled(); + RenderGraphItem(*info, state); + ImGui::EndDisabled(); + } else { + RenderGraphItem(*info, state); + } } } } @@ -170,13 +242,31 @@ void GraphBrowser::Render(AppState& state) { ImGui::Spacing(); auto filtered = GetFilteredGraphs(state.browser_filter, state.graph_search_query); + if (!state.show_all_charts) { + filtered.erase(std::remove_if(filtered.begin(), filtered.end(), + [&](const GraphInfo& graph) { + return !IsGraphAvailable(graph.kind, state, loader); + }), + filtered.end()); + } if (filtered.empty()) { - ImGui::TextDisabled("No graphs found"); + if (state.show_all_charts) { + ImGui::TextDisabled("No graphs found"); + } else { + ImGui::TextDisabled("No graphs with data (toggle Show All Chart Windows to view empty graphs)"); + } } else { ImGui::BeginChild("GraphList", ImVec2(0, 0), false); for (const auto& graph : filtered) { - RenderGraphItem(graph, state); + bool available = IsGraphAvailable(graph.kind, state, loader); + if (!available && state.show_all_charts) { + ImGui::BeginDisabled(); + RenderGraphItem(graph, state); + ImGui::EndDisabled(); + } else { + RenderGraphItem(graph, state); + } } ImGui::EndChild(); } diff --git a/apps/studio/src/ui/components/graph_browser.h b/apps/studio/src/ui/components/graph_browser.h index 5b69180..a1dd83d 100644 --- a/apps/studio/src/ui/components/graph_browser.h +++ b/apps/studio/src/ui/components/graph_browser.h @@ -4,6 +4,10 @@ #include #include "../../models/state.h" +namespace afs::viz { +class DataLoader; +} + namespace afs::viz::ui { // Graph metadata for browser display @@ -23,16 +27,21 @@ public: GraphBrowser(); // Render the graph browser sidebar - void Render(AppState& state); + void Render(AppState& state, const DataLoader& loader); // Get all available graphs const std::vector& GetAllGraphs() const { return all_graphs_; } // Get filtered graphs based on category and search - std::vector GetFilteredGraphs(GraphCategory category, const std::string& search) const; + std::vector GetFilteredGraphs(GraphCategory category, + const std::string& search) const; // Get graph info by kind const GraphInfo* GetGraphInfo(PlotKind kind) const; + + bool IsGraphAvailable(PlotKind kind, + const AppState& state, + const DataLoader& loader) const; // Get category name static const char* GetCategoryName(GraphCategory category); diff --git a/apps/studio/src/ui/core.cc b/apps/studio/src/ui/core.cc index ecb2905..adf28a9 100644 --- a/apps/studio/src/ui/core.cc +++ b/apps/studio/src/ui/core.cc @@ -245,6 +245,7 @@ const std::vector& PlotOptions() { {PlotKind::Effectiveness, "Domain Effectiveness"}, {PlotKind::Thresholds, "Threshold Sensitivity"}, {PlotKind::MountsStatus, "Local Mounts Status"}, + {PlotKind::DatasetInventory, "Dataset Inventory"}, }; return options; }