-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvisualize_dashboard.js
More file actions
192 lines (156 loc) · 7.24 KB
/
visualize_dashboard.js
File metadata and controls
192 lines (156 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/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));