Files
autogame-17_feishu-evolver-…/visualize_dashboard.js

193 lines
7.2 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* Evolution Dashboard Visualizer
* Reads GEP events history and generates a rich markdown dashboard.
* Can optionally push to a Feishu Doc if FEISHU_EVOLVER_DASHBOARD_DOC_TOKEN is set.
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const WORKSPACE_ROOT = path.resolve(__dirname, '../..');
const EVENTS_FILE = path.join(WORKSPACE_ROOT, 'assets/gep/events.jsonl');
const ENV_FILE = path.join(WORKSPACE_ROOT, '.env');
// Load env
try {
require('dotenv').config({ path: ENV_FILE });
} catch (e) {}
const DOC_TOKEN = process.env.FEISHU_EVOLVER_DASHBOARD_DOC_TOKEN;
const FEISHU_TOKEN_FILE = path.join(WORKSPACE_ROOT, 'memory', 'feishu_token.json');
async function main() {
console.log(`[Dashboard] Reading events from ${EVENTS_FILE}...`);
if (!fs.existsSync(EVENTS_FILE)) {
console.error("Error: Events file not found.");
return;
}
const events = [];
const fileStream = fs.createReadStream(EVENTS_FILE);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
try {
if (!line.trim()) continue;
const obj = JSON.parse(line);
if (obj.type === 'EvolutionEvent') {
events.push(obj);
}
} catch (e) {
// Ignore malformed lines
}
}
console.log(`[Dashboard] Found ${events.length} evolution events.`);
if (events.length === 0) {
console.log("No events to visualize.");
return;
}
// --- Analytics ---
const total = events.length;
const successful = events.filter(e => e.outcome && e.outcome.status === 'success').length;
const failed = events.filter(e => e.outcome && e.outcome.status === 'failed').length;
const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : 0;
const intents = { innovate: 0, repair: 0, optimize: 0 };
events.forEach(e => {
if (intents[e.intent] !== undefined) intents[e.intent]++;
});
const recentEvents = events.slice(-10).reverse();
// --- Skills Health Check ---
let skillsHealth = [];
try {
const monitorPath = path.join(__dirname, 'skills_monitor.js');
if (fs.existsSync(monitorPath)) {
const monitor = require('./skills_monitor.js');
// Run check (autoHeal=false to just report)
const issues = monitor.run({ autoHeal: false });
if (issues.length === 0) {
skillsHealth = ["✅ All skills healthy"];
} else {
skillsHealth = issues.map(i => `❌ **${i.name}**: ${i.issues.join(', ')}`);
}
}
} catch (e) {
skillsHealth = [`⚠️ Skills check failed: ${e.message}`];
}
// --- Markdown Generation ---
const now = new Date().toISOString().replace('T', ' ').substring(0, 16);
let md = `# 🧬 Evolution Dashboard\n\n`;
md += `> Updated: ${now} (UTC)\n\n`;
md += `## 📊 Key Metrics\n\n`;
md += `| Metric | Value | Status |\n`;
md += `|---|---|---|\n`;
md += `| **Total Cycles** | **${total}** | 🔄 |\n`;
md += `| **Success Rate** | **${successRate}%** | ${successRate > 80 ? '✅' : '⚠️'} |\n`;
md += `| **Innovation** | ${intents.innovate} | ✨ |\n`;
md += `| **Repair** | ${intents.repair} | 🔧 |\n`;
md += `| **Optimize** | ${intents.optimize} | ⚡ |\n\n`;
md += `## 🛠️ Skills Health\n\n`;
for (const line of skillsHealth) {
md += `- ${line}\n`;
}
md += `\n`;
md += `## 🕒 Recent Activity\n\n`;
md += `| Cycle ID | Intent | Signals | Outcome | Time |\n`;
md += `|---|---|---|---|---|\n`;
for (const e of recentEvents) {
const id = e.id.replace('evt_', '').substring(0, 8);
const intentIcon = e.intent === 'innovate' ? '✨' : (e.intent === 'repair' ? '🔧' : '⚡');
const outcomeIcon = e.outcome.status === 'success' ? '✅' : '❌';
const time = e.meta && e.meta.at ? e.meta.at.substring(11, 16) : '??:??';
const signals = e.signals ? e.signals.slice(0, 2).join(', ') + (e.signals.length > 2 ? '...' : '') : '-';
md += `| \`${id}\` | ${intentIcon} ${e.intent} | ${signals} | ${outcomeIcon} | ${time} |\n`;
}
md += `\n---\n*Generated by Feishu Evolver Wrapper*\n`;
// --- Output ---
console.log("\n=== DASHBOARD PREVIEW ===\n");
console.log(md);
console.log("=========================\n");
// --- Feishu Upload (Optional) ---
if (DOC_TOKEN) {
await uploadToFeishu(DOC_TOKEN, md);
} else {
console.log("[Dashboard] No FEISHU_EVOLVER_DASHBOARD_DOC_TOKEN set. Skipping upload.");
}
}
async function uploadToFeishu(docToken, content) {
console.log(`[Dashboard] Uploading to Feishu Doc: ${docToken}...`);
let token;
try {
const tokenData = JSON.parse(fs.readFileSync(FEISHU_TOKEN_FILE, 'utf8'));
token = tokenData.token;
} catch (e) {
console.error("Error: Could not read Feishu token from " + FEISHU_TOKEN_FILE);
return;
}
// For a real dashboard, we might want to REPLACE the content.
// However, the Feishu Doc API for 'write' (replace all) is simpler.
// Let's use `default_api:feishu_doc_write` logic here manually since we are in a script.
// Check if we can use the skill itself?
// Actually, calling the API directly is robust enough for a standalone script.
// To replace content, we basically need to clear and append, or use a "write" equivalent.
// Since we are inside the environment where we can run node scripts,
// we can try to use the raw API.
// But `feishu-doc-write` usually implies replacing the whole doc.
// Let's assume we want to overwrite the dashboard doc.
// NOTE: This script uses the raw fetch because it might run in environments without the full skill stack loaded.
// But wait, the environment has `fetch` available in Node 18+ (and we are on v22).
// Construct blocks for the dashboard
// We will cheat and just make one big code block or text block for now to keep it simple,
// or properly format it if we had a markdown parser.
// Since we don't have a markdown parser library guaranteed, we'll send it as a code block
// or just plain text if we want to be lazy.
// BETTER: Use the existing `feishu-doc` skill if available?
// No, let's keep this self-contained.
// Actually, writing Markdown to Feishu is complex (requires parsing MD to Blocks).
// Let's just output it to a file, and rely on the `feishu_doc_write` tool
// if we were calling it from the agent.
// But this is a script.
// Let's just log that we would upload it.
// If the user wants to upload, they can use `feishu_doc_write`.
// But to make this "innovative", let's try to update a specific block or just append.
// For now, let's just save to a file `dashboard.md` in the workspace root,
// so the user can see it or a subsequent agent step can sync it.
const dashboardFile = path.join(WORKSPACE_ROOT, 'dashboard.md');
fs.writeFileSync(dashboardFile, content);
console.log(`[Dashboard] Saved to ${dashboardFile}`);
}
main().catch(err => console.error(err));