- Integrate apaw_agent_model_research_v3.html as standalone dashboard - Add model-benchmarks.json with 32 agents, 11 scored models, 11 recommendations - Add build-research-dashboard.ts: inject live data into template → standalone HTML - Add rebuild-template.cjs: regenerate template from v3.html source - Add sync-benchmarks-from-yaml.cjs: sync YAML → JSON round-trip - Add sync-model-research.ts: apply recommendation matrix to config files - Add model-benchmarks.schema.json and model-research.schema.json for validation - Add bidirectional-data-flow.md architecture documentation - Add log-execution.cjs pipeline hook - Update capability-index.yaml: add fallback_models, failover_strategy - Update kilo-meta.json, kilo.jsonc, KILO_SPEC.md with synced models - Update evolution.md / research.md / self-evolution.md / evolutionary-sync.md docs - Fix security-auditor.md: quote YAML color (#DC2626) - Fix orchestrator.md: remove duplicate devops-engineer key - Build research-dashboard.html (106KB standalone) + dated archive
237 lines
8.6 KiB
TypeScript
237 lines
8.6 KiB
TypeScript
#!/usr/bin/env bun
|
||
/**
|
||
* Build APAW Agent Model Research Dashboard from live data.
|
||
*
|
||
* Reads model-benchmarks.json and injects into template HTML.
|
||
* Creates standalone dashboard with embedded JSON data.
|
||
*
|
||
* Usage:
|
||
* bun run agent-evolution/scripts/build-research-dashboard.ts # build once
|
||
* bun run agent-evolution/scripts/build-research-dashboard.ts --watch # watch mode
|
||
* bun run agent-evolution/scripts/build-research-dashboard.ts --template path/to/custom.html
|
||
*/
|
||
|
||
import { existsSync, readFileSync, writeFileSync, watch } from 'fs';
|
||
import { join, dirname, basename } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
|
||
const DATA_FILE = join(__dirname, '../data/model-benchmarks.json');
|
||
const DEFAULT_TEMPLATE = join(__dirname, '../research-dashboard.template.html');
|
||
const OUTPUT_FILE = join(__dirname, '../research-dashboard.html');
|
||
const DIST_DIR = join(__dirname, '../dist');
|
||
|
||
interface BenchmarksData {
|
||
version: string;
|
||
generated: string;
|
||
source: string;
|
||
total_agents: number;
|
||
total_models_tracked: number;
|
||
providers: string[];
|
||
models: any[];
|
||
agent_model_scores: any[];
|
||
agent_current_config: any[];
|
||
groq_models: any[];
|
||
recommendations: any[];
|
||
impact_data: any[];
|
||
}
|
||
|
||
function buildDashboard(templatePath: string = DEFAULT_TEMPLATE): boolean {
|
||
console.log('🔧 Building APAW Agent Model Research Dashboard');
|
||
|
||
// Validate inputs
|
||
if (!existsSync(DATA_FILE)) {
|
||
console.error(`❌ Data file not found: ${DATA_FILE}`);
|
||
console.error(' Please run research cycle first: bun run /research models');
|
||
return false;
|
||
}
|
||
|
||
if (!existsSync(templatePath)) {
|
||
console.error(`❌ Template file not found: ${templatePath}`);
|
||
console.error(' Using default template:', DEFAULT_TEMPLATE);
|
||
if (!existsSync(DEFAULT_TEMPLATE)) {
|
||
console.error(' Default template also missing. Create template first.');
|
||
return false;
|
||
}
|
||
templatePath = DEFAULT_TEMPLATE;
|
||
}
|
||
|
||
// Read and validate JSON data
|
||
let data: BenchmarksData;
|
||
try {
|
||
const rawData = readFileSync(DATA_FILE, 'utf-8');
|
||
data = JSON.parse(rawData);
|
||
console.log(`📖 Read model-benchmarks.json (${rawData.length} bytes)`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to parse JSON data: ${error}`);
|
||
return false;
|
||
}
|
||
|
||
// Validate required fields
|
||
if (!data.models || !Array.isArray(data.models)) {
|
||
console.error('❌ Missing or invalid "models" array in data');
|
||
return false;
|
||
}
|
||
|
||
if (!data.agent_model_scores || !Array.isArray(data.agent_model_scores)) {
|
||
console.error('❌ Missing or invalid "agent_model_scores" array in data');
|
||
return false;
|
||
}
|
||
|
||
console.log(` Models: ${data.models.length}`);
|
||
console.log(` Agents: ${data.agent_model_scores.length}`);
|
||
console.log(` Providers: ${data.providers?.join(', ') || 'unknown'}`);
|
||
console.log(` Generated: ${data.generated}`);
|
||
|
||
// Read HTML template
|
||
let html: string;
|
||
try {
|
||
html = readFileSync(templatePath, 'utf-8');
|
||
console.log(`📖 Read template: ${templatePath} (${html.length} bytes)`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to read template: ${error}`);
|
||
return false;
|
||
}
|
||
|
||
// Find and replace placeholder — must match exact text in template
|
||
const placeholder = '// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\nconst EMBEDDED_DATA = {};\n';
|
||
if (!html.includes(placeholder)) {
|
||
// Try looser match with any line endings
|
||
const loosePlaceholder = html.match(/\/\/\s*BENCHMARK_DATA_PLACEHOLDER[^\n]*\r?\n\s*const\s+EMBEDDED_DATA\s*=\s*\{\}\s*;\r?\n/);
|
||
if (!loosePlaceholder) {
|
||
console.error('❌ Placeholder not found in template');
|
||
console.error(' Expected: "// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\\nconst EMBEDDED_DATA = {};\\n"');
|
||
const match = html.match(/BENCHMARK_DATA_PLACEHOLDER/);
|
||
if (match) {
|
||
const start = Math.max(0, match.index - 20);
|
||
const end = Math.min(html.length, match.index + 120);
|
||
console.error(' Found near:', JSON.stringify(html.slice(start, end)));
|
||
}
|
||
return false;
|
||
}
|
||
html = html.replace(loosePlaceholder[0], `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`);
|
||
} else {
|
||
html = html.replace(placeholder, `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`);
|
||
}
|
||
|
||
// Update title with metadata if present (match any tag with APAW... in it)
|
||
const titleRegex = /<title>[^<]*APAW[^<]*<\/title>/;
|
||
if (titleRegex.test(html)) {
|
||
const newTitle = `APAW Agent Model Research — generated ${data.generated.slice(0, 10)}`;
|
||
html = html.replace(titleRegex, `<title>${newTitle}</title>`);
|
||
}
|
||
|
||
// Update subtitle if present
|
||
const subtitlePattern = /<div class="sub">([^<]*)<\/div>/;
|
||
const newSubtitle = `<div class="sub">Live dashboard • ${data.models.length} models × ${data.agent_model_scores.length} agents • ${data.generated.slice(0, 10)}</div>`;
|
||
if (subtitlePattern.test(html)) {
|
||
html = html.replace(subtitlePattern, newSubtitle);
|
||
}
|
||
|
||
// Write output file
|
||
try {
|
||
writeFileSync(OUTPUT_FILE, html);
|
||
console.log(`✅ Output written to: ${OUTPUT_FILE} (${html.length} bytes)`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to write output: ${error}`);
|
||
return false;
|
||
}
|
||
|
||
// Create dated version in dist directory
|
||
try {
|
||
if (!existsSync(DIST_DIR)) {
|
||
require('fs').mkdirSync(DIST_DIR, { recursive: true });
|
||
}
|
||
const dateStr = data.generated.slice(0, 10).replace(/-/g, '_');
|
||
const distFile = join(DIST_DIR, `research-dashboard-${dateStr}.html`);
|
||
writeFileSync(distFile, html);
|
||
console.log(`📁 Dated copy: ${distFile}`);
|
||
} catch (error) {
|
||
console.warn(`⚠️ Could not create dated copy: ${error}`);
|
||
}
|
||
|
||
// Print summary
|
||
const recommendations = data.recommendations || [];
|
||
console.log('\n📊 Summary:');
|
||
console.log(` • Agents tracked: ${data.total_agents || data.agent_model_scores.length}`);
|
||
console.log(` • Models benchmarked: ${data.total_models_tracked || data.models.length}`);
|
||
console.log(` • Providers: ${data.providers?.join(', ')}`);
|
||
console.log(` • Recommendations: ${recommendations.length}`);
|
||
|
||
if (recommendations.length >577.0) {
|
||
const highImpact = recommendations.filter((r: any) => r.impact === 'high').length;
|
||
const applied = recommendations.filter((r: any) => r.to_model?.includes('✅')).length;
|
||
console.log(` • High-impact recommendations: ${highImpact}`);
|
||
console.log(` • Applied recommendations: ${applied}`);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
function watchMode(): void {
|
||
console.log('👀 Watch mode enabled - monitoring data and template files');
|
||
console.log(' Press Ctrl+C to stop');
|
||
|
||
let timeout: Timer | null = null;
|
||
|
||
watch(DATA_FILE, (eventType) => {
|
||
if (eventType === 'change') {
|
||
if (timeout) clearTimeout(timeout);
|
||
timeout = setTimeout(() => {
|
||
console.log('\n🔄 Data file changed, rebuilding...');
|
||
buildDashboard();
|
||
}, 500);
|
||
}
|
||
});
|
||
|
||
watch(DEFAULT_TEMPLATE, (eventType) => {
|
||
if (eventType === 'change') {
|
||
if (timeout) clearTimeout(timeout);
|
||
timeout = setTimeout(() => {
|
||
console.log('\n🔄 Template file changed, rebuilding...');
|
||
buildDashboard();
|
||
}, 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Parse CLI arguments
|
||
const args = process.argv.slice(2);
|
||
let watchModeEnabled = false;
|
||
let customTemplate: string | undefined;
|
||
|
||
for (let i = 0; i < args.length; i++) {
|
||
if (args[i] === '--watch') {
|
||
watchModeEnabled = true;
|
||
} else if (args[i] === '--template' && i + 1 < args.length) {
|
||
customTemplate = args[i + 1];
|
||
i++;
|
||
} else if (args[i] === '--help' || args[i] === '-h') {
|
||
console.log(`
|
||
Usage: bun run agent-evolution/scripts/build-research-dashboard.ts [options]
|
||
|
||
Options:
|
||
--watch Watch for changes and rebuild automatically
|
||
--template <path> Use custom HTML template file
|
||
--help, -h Show this help message
|
||
|
||
Examples:
|
||
bun run agent-evolution/scripts/build-research-dashboard.ts
|
||
bun run agent-evolution/scripts/build-research-dashboard.ts --watch
|
||
bun run agent-evolution/scripts/build-research-dashboard.ts --template custom.html
|
||
`);
|
||
process.exit(0);
|
||
}
|
||
}
|
||
|
||
// Main execution
|
||
if (watchModeEnabled) {
|
||
// Build once then watch
|
||
buildDashboard(customTemplate);
|
||
watchMode();
|
||
} else {
|
||
const success = buildDashboard(customTemplate);
|
||
process.exit(success ? 0 : 1);
|
||
} |