studio: improve data-backed graphs

This commit is contained in:
scawful
2025-12-30 11:56:31 -05:00
parent 2711cf1658
commit ce6daf1b9b
10 changed files with 266 additions and 18 deletions

View File

@@ -113,6 +113,7 @@ void App::RefreshData(const char* reason) {
bool ok = loader_.Refresh(); bool ok = loader_.Refresh();
state_.last_refresh_time = glfwGetTime(); state_.last_refresh_time = glfwGetTime();
SyncDataBackedState(); SyncDataBackedState();
EnsureActiveGraph();
const auto& status = loader_.GetLastStatus(); const auto& status = loader_.GetLastStatus();
std::string msg; 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() { void App::SeedDefaultState() {
ui::AppendLog(state_, "system", "AFS Studio environment ready.", "system"); ui::AppendLog(state_, "system", "AFS Studio environment ready.", "system");
state_.sparkline_data.resize(30, 0.0f); state_.sparkline_data.resize(30, 0.0f);
@@ -367,7 +397,7 @@ void App::RenderLayout() {
if (state_.show_graph_browser) { if (state_.show_graph_browser) {
ImGui::Begin("GraphBrowser", &state_.show_graph_browser, ImGui::Begin("GraphBrowser", &state_.show_graph_browser,
ImGuiWindowFlags_NoCollapse); ImGuiWindowFlags_NoCollapse);
graph_browser_.Render(state_); graph_browser_.Render(state_, loader_);
ImGui::End(); ImGui::End();
} }

View File

@@ -38,10 +38,11 @@ class App {
const DataLoader& loader() const { return loader_; } const DataLoader& loader() const { return loader_; }
private: private:
void RefreshData(const char* reason); void RefreshData(const char* reason);
void SeedDefaultState(); void SeedDefaultState();
void SyncDataBackedState(); void SyncDataBackedState();
void TickSimulatedMetrics(float dt); void TickSimulatedMetrics(float dt);
void EnsureActiveGraph();
void RenderFrame(); void RenderFrame();
void RenderLayout(); void RenderLayout();

View File

@@ -40,7 +40,17 @@ RegistryReader::RegistryReader(const std::filesystem::path& registry_path)
: registry_path_(registry_path) {} : registry_path_(registry_path) {}
std::filesystem::path RegistryReader::ResolveDefaultPath() const { 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 { bool RegistryReader::Exists() const {

View File

@@ -22,6 +22,8 @@ using json = nlohmann::json;
constexpr size_t kTrendWindow = 5; constexpr size_t kTrendWindow = 5;
constexpr float kPi = 3.14159265f; constexpr float kPi = 3.14159265f;
std::optional<std::filesystem::path> ResolveTrunkRoot();
std::optional<std::filesystem::path> ResolveHafsScawfulRoot() { std::optional<std::filesystem::path> ResolveHafsScawfulRoot() {
const char* env_root = std::getenv("AFS_SCAWFUL_ROOT"); const char* env_root = std::getenv("AFS_SCAWFUL_ROOT");
if (env_root && env_root[0] != '\0') { if (env_root && env_root[0] != '\0') {
@@ -318,6 +320,15 @@ bool DataLoader::Refresh() {
} }
LoadResult resource = LoadResourceIndex(&next_resource_index); 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) { if (!resource.found) {
resource_index_ = ResourceIndexData{}; resource_index_ = ResourceIndexData{};
resource_index_error_ = "resource_index.json not found"; resource_index_error_ = "resource_index.json not found";
@@ -329,6 +340,15 @@ bool DataLoader::Refresh() {
} }
LoadResult registry = LoadDatasetRegistry(&next_dataset_registry); 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) { if (!registry.found) {
dataset_registry_ = DatasetRegistryData{}; dataset_registry_ = DatasetRegistryData{};
dataset_registry_error_ = "dataset_registry.json not found"; dataset_registry_error_ = "dataset_registry.json not found";
@@ -340,6 +360,15 @@ bool DataLoader::Refresh() {
} }
LoadResult context_graph = LoadContextGraph(&next_context_graph); 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) { if (!context_graph.found) {
context_graph_ = ContextGraphData{}; context_graph_ = ContextGraphData{};
context_graph_error_ = "afs_graph.json not found"; context_graph_error_ = "afs_graph.json not found";
@@ -386,7 +415,10 @@ bool DataLoader::Refresh() {
has_data_ = !quality_trends_.empty() || !generator_stats_.empty() || has_data_ = !quality_trends_.empty() || !generator_stats_.empty() ||
!embedding_regions_.empty() || !training_runs_.empty() || !embedding_regions_.empty() || !training_runs_.empty() ||
!optimization_data_.domain_effectiveness.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; last_error_ = last_status_.last_error;
return last_status_.AnyOk() || (!(last_status_.FoundCount() > 0) && has_data_); return last_status_.AnyOk() || (!(last_status_.FoundCount() > 0) && has_data_);

View File

@@ -150,19 +150,30 @@ struct LoadStatus {
bool active_ok = false; bool active_ok = false;
bool training_found = false; bool training_found = false;
bool training_ok = 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; int error_count = 0;
std::string last_error; std::string last_error;
std::string last_error_source; std::string last_error_source;
int FoundCount() const { int FoundCount() const {
return static_cast<int>(quality_found) + static_cast<int>(active_found) + return static_cast<int>(quality_found) + static_cast<int>(active_found) +
static_cast<int>(training_found); static_cast<int>(training_found) + static_cast<int>(resource_index_found) +
static_cast<int>(dataset_registry_found) + static_cast<int>(context_graph_found);
} }
int OkCount() const { int OkCount() const {
return static_cast<int>(quality_ok) + static_cast<int>(active_ok) + return static_cast<int>(quality_ok) + static_cast<int>(active_ok) +
static_cast<int>(training_ok); static_cast<int>(training_ok) + static_cast<int>(resource_index_ok) +
static_cast<int>(dataset_registry_ok) + static_cast<int>(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. /// Loads training data from JSON files.

View File

@@ -37,6 +37,7 @@ enum class PlotKind {
Effectiveness, Effectiveness,
Thresholds, Thresholds,
MountsStatus, MountsStatus,
DatasetInventory,
}; };
enum class GraphViewMode { Single, Compare, Overview }; enum class GraphViewMode { Single, Compare, Overview };
@@ -238,7 +239,7 @@ struct AppState {
bool show_plot_legends = true; bool show_plot_legends = true;
bool show_plot_markers = true; bool show_plot_markers = true;
bool data_scientist_mode = false; bool data_scientist_mode = false;
bool show_all_charts = true; bool show_all_charts = false;
float pulse_timer = 0.0f; float pulse_timer = 0.0f;
int custom_grid_rows = 2; int custom_grid_rows = 2;
int custom_grid_columns = 2; int custom_grid_columns = 2;

View File

@@ -14,6 +14,7 @@ namespace viz {
namespace ui { namespace ui {
void RenderMountsChart(AppState& state, const DataLoader& loader); void RenderMountsChart(AppState& state, const DataLoader& loader);
void RenderKnowledgeGraphChart(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); 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<const char*> labels;
std::vector<float> sizes_mb;
std::vector<float> file_counts;
std::vector<std::string> 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<float>(dataset.size_bytes) / (1024.0f * 1024.0f));
file_counts.push_back(static_cast<float>(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<ImPlotAxisFlags>(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<double>(labels.size() - 1),
static_cast<int>(labels.size()), labels.data());
}
HandlePlotContextMenu(PlotKind::DatasetInventory, state);
ImPlot::SetNextFillStyle(GetSeriesColor(3), 0.75f);
ImPlot::PlotBars("Size (MB)", sizes_mb.data(),
static_cast<int>(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<int>(file_counts.size()));
ImPlot::EndPlot();
}
ImPlot::PopStyleColor(2);
ImPlot::PopStyleVar(6);
}
void RenderRejectionChart(AppState& state, const DataLoader& loader) { void RenderRejectionChart(AppState& state, const DataLoader& loader) {
RenderChartHeader(PlotKind::Rejections, RenderChartHeader(PlotKind::Rejections,
"REJECTION REASONS", "REJECTION REASONS",
@@ -1075,6 +1135,9 @@ void RenderPlotByKind(PlotKind kind, AppState& state, const DataLoader& loader)
case PlotKind::MountsStatus: case PlotKind::MountsStatus:
RenderMountsChart(state, loader); RenderMountsChart(state, loader);
break; break;
case PlotKind::DatasetInventory:
RenderDatasetInventoryChart(state, loader);
break;
default: default:
break; break;
} }

View File

@@ -1,6 +1,7 @@
#include "graph_browser.h" #include "graph_browser.h"
#include "../core.h" #include "../core.h"
#include "../../icons.h" #include "../../icons.h"
#include "../../data_loader.h"
#include <imgui.h> #include <imgui.h>
#include <algorithm> #include <algorithm>
@@ -15,6 +16,7 @@ void GraphBrowser::InitializeGraphRegistry() {
// Training Category // Training Category
{PlotKind::TrainingLoss, "Training Loss", "Loss curves over training steps", GraphCategory::Training, true, true, true, true}, {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::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 // Quality Category
{PlotKind::QualityTrends, "Quality Trends", "Data quality trends by domain", GraphCategory::Quality, true, true, true, true}, {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; 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)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8));
// Search bar // Search bar
@@ -136,7 +188,17 @@ void GraphBrowser::Render(AppState& state) {
for (auto kind : state.graph_bookmarks) { for (auto kind : state.graph_bookmarks) {
const GraphInfo* info = GetGraphInfo(kind); const GraphInfo* info = GetGraphInfo(kind);
if (info) { 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) { for (auto kind : recent_unique) {
const GraphInfo* info = GetGraphInfo(kind); const GraphInfo* info = GetGraphInfo(kind);
if (info) { 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(); ImGui::Spacing();
auto filtered = GetFilteredGraphs(state.browser_filter, state.graph_search_query); 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()) { 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 { } else {
ImGui::BeginChild("GraphList", ImVec2(0, 0), false); ImGui::BeginChild("GraphList", ImVec2(0, 0), false);
for (const auto& graph : filtered) { 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(); ImGui::EndChild();
} }

View File

@@ -4,6 +4,10 @@
#include <vector> #include <vector>
#include "../../models/state.h" #include "../../models/state.h"
namespace afs::viz {
class DataLoader;
}
namespace afs::viz::ui { namespace afs::viz::ui {
// Graph metadata for browser display // Graph metadata for browser display
@@ -23,16 +27,21 @@ public:
GraphBrowser(); GraphBrowser();
// Render the graph browser sidebar // Render the graph browser sidebar
void Render(AppState& state); void Render(AppState& state, const DataLoader& loader);
// Get all available graphs // Get all available graphs
const std::vector<GraphInfo>& GetAllGraphs() const { return all_graphs_; } const std::vector<GraphInfo>& GetAllGraphs() const { return all_graphs_; }
// Get filtered graphs based on category and search // Get filtered graphs based on category and search
std::vector<GraphInfo> GetFilteredGraphs(GraphCategory category, const std::string& search) const; std::vector<GraphInfo> GetFilteredGraphs(GraphCategory category,
const std::string& search) const;
// Get graph info by kind // Get graph info by kind
const GraphInfo* GetGraphInfo(PlotKind kind) const; const GraphInfo* GetGraphInfo(PlotKind kind) const;
bool IsGraphAvailable(PlotKind kind,
const AppState& state,
const DataLoader& loader) const;
// Get category name // Get category name
static const char* GetCategoryName(GraphCategory category); static const char* GetCategoryName(GraphCategory category);

View File

@@ -245,6 +245,7 @@ const std::vector<PlotOption>& PlotOptions() {
{PlotKind::Effectiveness, "Domain Effectiveness"}, {PlotKind::Effectiveness, "Domain Effectiveness"},
{PlotKind::Thresholds, "Threshold Sensitivity"}, {PlotKind::Thresholds, "Threshold Sensitivity"},
{PlotKind::MountsStatus, "Local Mounts Status"}, {PlotKind::MountsStatus, "Local Mounts Status"},
{PlotKind::DatasetInventory, "Dataset Inventory"},
}; };
return options; return options;
} }