xykj-plus/claude-ecc/log-analyzer/vite.config.ts
2026-06-13 13:48:51 +08:00

116 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineConfig, type Plugin } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'node:path';
import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import type { IncomingMessage, ServerResponse } from 'node:http';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 日志目录:向上一级到项目根,再进 .claude/logs
const LOGS_DIR = path.resolve(__dirname, '../.claude/logs');
function sendJson(res: ServerResponse, data: unknown, status = 200) {
res.statusCode = status;
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.end(JSON.stringify(data));
}
function logApiPlugin(): Plugin {
return {
name: 'log-api',
configureServer(server) {
server.middlewares.use(
async (req: IncomingMessage, res: ServerResponse, next: () => void) => {
const url = req.url ?? '';
if (req.method !== 'GET' || !url.startsWith('/api/')) return next();
try {
// GET /api/folders —— 列出日志目录下的所有子文件夹
if (url === '/api/folders') {
const urlObj = new URL(`http://localhost${url}`);
const baseDir = urlObj.searchParams.get('base') ? decodeURIComponent(urlObj.searchParams.get('base')!) : LOGS_DIR;
let entries: string[] = [];
try { entries = await fs.readdir(baseDir); } catch { /* logs dir 可能不存在 */ }
const folders = await Promise.all(
entries
.filter(async (e) => {
try {
const stat = await fs.stat(path.join(baseDir, e));
return stat.isDirectory();
} catch { return false; }
})
.map(async (folder) => {
const folderPath = path.join(baseDir, folder);
const files = (await fs.readdir(folderPath)).filter(f => f.startsWith('trace-') && f.endsWith('.jsonl'));
return { name: folder, sessionCount: files.length };
})
);
folders.sort((a, b) => a.name.localeCompare(b.name));
return sendJson(res, folders);
}
// GET /api/sessions —— 列出所有 trace-*.jsonl支持 base 和 folder 查询参数)
if (url.startsWith('/api/sessions') && !url.match(/^\/api\/sessions\//)) {
const urlObj = new URL(`http://localhost${url}`);
const baseDir = urlObj.searchParams.get('base') ? decodeURIComponent(urlObj.searchParams.get('base')!) : LOGS_DIR;
const folder = urlObj.searchParams.get('folder');
const targetDir = folder ? path.join(baseDir, folder) : baseDir;
let files: string[] = [];
try { files = await fs.readdir(targetDir); } catch { /* dir 可能不存在 */ }
const sessions = await Promise.all(
files
.filter(f => f.startsWith('trace-') && f.endsWith('.jsonl'))
.map(async (f) => {
const sid = f.slice('trace-'.length, -'.jsonl'.length);
const filePath = path.join(targetDir, f);
const [stat, content] = await Promise.all([
fs.stat(filePath),
fs.readFile(filePath, 'utf8'),
]);
const lines = content.split('\n').filter(l => l.trim().length > 0);
let firstTs: string | null = null;
let lastTs: string | null = null;
try { firstTs = (JSON.parse(lines[0]) as { ts: string }).ts; } catch { /* skip */ }
try { lastTs = (JSON.parse(lines[lines.length - 1]) as { ts: string }).ts; } catch { /* skip */ }
return { sid, filename: f, folder: folder ?? '', mtime: stat.mtime, eventCount: lines.length, firstTs, lastTs };
})
);
sessions.sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime());
return sendJson(res, sessions);
}
// GET /api/sessions/:sid —— 读取单个 trace 文件
const sessionMatch = url.match(/^\/api\/sessions\/([^/?]+)$/);
if (sessionMatch) {
const sid = sessionMatch[1];
const filePath = path.join(LOGS_DIR, `trace-${sid}.jsonl`);
let content: string;
try {
content = await fs.readFile(filePath, 'utf8');
} catch {
return sendJson(res, { error: 'Session not found' }, 404);
}
const records = content
.split('\n')
.filter(l => l.trim().length > 0)
.flatMap(l => { try { return [JSON.parse(l)]; } catch { return []; } });
return sendJson(res, records);
}
next();
} catch (e) {
sendJson(res, { error: String(e) }, 500);
}
}
);
},
};
}
export default defineConfig({
plugins: [react(), logApiPlugin()],
server: { port: 5177 },
});