Skip to content

Commit 43a10d9

Browse files
authored
Merge pull request #3 from twilio-labs/add-dashboard-cli
add dashboard as cli command
2 parents e2d563f + 4d9dac7 commit 43a10d9

File tree

6 files changed

+122
-101
lines changed

6 files changed

+122
-101
lines changed

dashboard/server.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
],
1717
"scripts": {
1818
"build": "tsc -p tsconfig.build.json && tsc-esm-fix --ext .js ./dist && chmod +x dist/cli.js",
19-
"cli:extract-metrics": "node --import tsx/esm src/cli.ts extract-metrics",
20-
"cli:generate-summary": "node --import tsx/esm src/cli.ts generate-summary",
21-
"dashboard": "node --watch --import tsx/esm dashboard/server.ts",
22-
"lint": "eslint src/**/*.ts dashboard/**/*.ts",
23-
"lint:fix": "eslint src/**/*.ts dashboard/**/*.ts --fix",
19+
"cli:extract-metrics": "node --watch --import tsx/esm src/cli.ts extract-metrics",
20+
"cli:generate-summary": "node --watch --import tsx/esm src/cli.ts generate-summary",
21+
"cli:dashboard": "node --watch --import tsx/esm src/cli.ts dashboard",
22+
"lint": "eslint src/**/*.ts",
23+
"lint:fix": "eslint src/**/*.ts --fix",
2424
"prepare": "husky"
2525
},
2626
"lint-staged": {

src/cli.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { hideBin } from 'yargs/helpers';
33
import yargs from 'yargs/yargs';
44

5+
import startDashboard from './dashboard';
56
import ExtractMetrics from './extract-metrics';
67
import GenerateSummary from './generate-summary';
78
import { logger } from './utils';
@@ -54,6 +55,20 @@ yargs(hideBin(process.argv))
5455
}
5556
},
5657
)
58+
.command(
59+
'dashboard',
60+
'Start the dashboard web server',
61+
() => {},
62+
async () => {
63+
try {
64+
const subArgs = process.argv.slice(0, 2).concat(process.argv.slice(3));
65+
startDashboard(subArgs);
66+
} catch (error) {
67+
logger.error('Unexpected error while starting server:', error);
68+
process.exit(1);
69+
}
70+
},
71+
)
5772
.demandCommand(1, 'You must specify a command to run')
5873
.help()
5974
.alias('help', 'h')

src/dashboard.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import fs from 'fs';
2+
import http from 'http';
3+
import path from 'path';
4+
import { fileURLToPath } from 'url';
5+
6+
import { hideBin } from 'yargs/helpers';
7+
import yargs from 'yargs/yargs';
8+
9+
import { logger } from './utils';
10+
11+
interface MimeTypes {
12+
[key: string]: string;
13+
}
14+
15+
const MIME_TYPES: MimeTypes = {
16+
'.html': 'text/html',
17+
'.js': 'text/javascript',
18+
'.css': 'text/css',
19+
'.json': 'application/json',
20+
'.png': 'image/png',
21+
'.jpg': 'image/jpg',
22+
'.gif': 'image/gif',
23+
'.svg': 'image/svg+xml',
24+
'.ico': 'image/x-icon',
25+
};
26+
const dirName = path.dirname(fileURLToPath(import.meta.url));
27+
const publicDir = path.join(dirName, '../public');
28+
29+
const getPath = (url: string) => {
30+
if (url === '/') {
31+
return path.join(publicDir, 'dashboard.html');
32+
}
33+
34+
if (url === '/index.js') {
35+
return path.join(publicDir, 'index.js');
36+
}
37+
38+
if (url === '/index.css') {
39+
return path.join(publicDir, 'index.css');
40+
}
41+
42+
if (url.startsWith('/metrics/')) {
43+
return url === '/metrics/summary.json'
44+
? path.join(publicDir, '../metrics', url.replace('/metrics/', ''))
45+
: path.join(publicDir, '../metrics/tasks', url.replace('/metrics/', ''));
46+
}
47+
48+
if (url.startsWith('/docs/')) {
49+
return path.join(publicDir, url);
50+
}
51+
52+
return path.join(publicDir, url);
53+
};
54+
55+
const server = http.createServer((req, res) => {
56+
if (!req.url) {
57+
res.writeHead(400);
58+
res.end('Bad request: URL missing');
59+
return;
60+
}
61+
62+
const filePath = getPath(req.url);
63+
const extname = path.extname(filePath);
64+
const contentType = MIME_TYPES[extname] || 'application/octet-stream';
65+
66+
fs.readFile(filePath, (err, content) => {
67+
if (err) {
68+
if (err.code === 'ENOENT') {
69+
res.writeHead(404);
70+
res.end('404 Not Found');
71+
return;
72+
}
73+
74+
res.writeHead(500);
75+
res.end(`Server Error: ${err.code}`);
76+
return;
77+
}
78+
79+
res.writeHead(200, { 'Content-Type': contentType });
80+
res.end(content, 'utf-8');
81+
});
82+
});
83+
84+
export default function startServer(argv: string[]) {
85+
const parsedArgs = yargs(hideBin(argv))
86+
.option('port', {
87+
alias: 'p',
88+
type: 'number',
89+
description: 'Port to run the server on',
90+
default: 3001,
91+
})
92+
.help()
93+
.alias('help', 'h')
94+
.parseSync();
95+
96+
const { port } = parsedArgs;
97+
98+
server.listen(port, () => {
99+
logger.info(`Server running at http://localhost:${port}/`);
100+
logger.info('Press Ctrl+C to stop the server');
101+
});
102+
}

src/metrics/chat-processor.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class ChatProcessor {
3939
*/
4040
async process(): Promise<TaskSegment[]> {
4141
try {
42-
// Initialize and validate in a single step
4342
const initialized = await this.initialize();
4443
if (!initialized) {
4544
logger.error(
@@ -48,7 +47,6 @@ class ChatProcessor {
4847
return [];
4948
}
5049

51-
// Identify test type with validation
5250
const testType = this.getTestType();
5351
if (!testType) {
5452
logger.warn(
@@ -57,7 +55,6 @@ class ChatProcessor {
5755
return [];
5856
}
5957

60-
// Extract and process task segments
6158
return await this.extractTaskSegments(testType);
6259
} catch (error) {
6360
logger.error(
@@ -122,30 +119,24 @@ class ChatProcessor {
122119
return Promise.resolve([]);
123120
}
124121

125-
// Find task boundaries
126122
const taskBoundaries = findTaskBoundaries(
127123
this.uiMessages,
128124
this.directoryId,
129125
testType,
130126
);
131127

132-
// If no task boundaries found, return empty array
133128
if (taskBoundaries.length === 0) {
134129
logger.warn(`No task boundaries found in ${this.directoryId}`);
135130
return Promise.resolve([]);
136131
}
137132

138-
// Get the last valid message timestamp
139133
const lastValidMessageTs = getLastValidMessageTimestamp(this.uiMessages);
140-
141-
// Validate and set end boundaries
142134
const validatedBoundaries = validateTaskBoundaries(
143135
taskBoundaries,
144136
lastValidMessageTs,
145137
this.uiMessages.length,
146138
);
147139

148-
// Process task segments in parallel
149140
return Promise.all(
150141
validatedBoundaries.map((task) => this.processTaskSegment(task)),
151142
);
@@ -177,32 +168,25 @@ class ChatProcessor {
177168
`Processing ${messages.length} messages for task ${task.taskNumber}`,
178169
);
179170

180-
// Filter messages to include only relevant ones
181171
const relevantMessages = filterRelevantMessages(messages);
182-
183-
// Create timestamp boundaries for filtering API entries
184172
const [startBoundary, endBoundary] = createTimestampBoundaries(
185173
task.startTime,
186174
task.endTime as number,
187175
);
188-
189-
// Filter API entries that fall within this task's time boundaries
190176
const apiEntries = this.apiHistory.filter((entry) => {
191177
const timestamp = getTimestampFromApiEntry(entry);
192178
return (
193179
timestamp && timestamp >= startBoundary && timestamp <= endBoundary
194180
);
195181
});
196182

197-
// Extract conversation history index from UI messages
198183
for (const msg of relevantMessages) {
199184
const index = extractConversationHistoryIndex(msg);
200185
if (index !== undefined) {
201186
msg.conversationHistoryIndex = index;
202187
}
203188
}
204189

205-
// Return the processed task segment
206190
return {
207191
...task,
208192
apiCalls: apiEntries,

src/metrics/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// Types and interfaces for metrics processing
2-
31
export const CONTROL_MARKER = 'control_instructions.md';
42
export const MCP_MARKER = 'mcp_instructions.md';
53
export const TASK_START_MARKER = 'Complete Task';

0 commit comments

Comments
 (0)