feat(insight): Implement static insight generation and visualization

- Add HTML template for insights display.
- Create JavaScript application logic for rendering insights.
- Introduce CSS styles for layout and design.
- Develop a test generator for validating the static insight generator.
- Define TypeScript interfaces for structured insight data.
- Refactor insight command to generate insights and open in browser.
- Remove the need for a server process by generating static files directly.
This commit is contained in:
DragonnZhang 2026-01-23 17:30:41 +08:00
parent 18a21545ea
commit 0e55800941
27 changed files with 1415 additions and 34034 deletions

View file

@ -0,0 +1,94 @@
/**
* @license
* Copyright 2025 Qwen Code
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'fs/promises';
import path from 'path';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import type { InsightData, StaticInsightTemplateData } from '../types/StaticInsightTypes.js';
export class TemplateRenderer {
private templateDir: string;
constructor() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
this.templateDir = path.join(__dirname, '..', 'templates');
}
// Safe JSON stringification to prevent XSS
private safeJsonStringify(data: any): string {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
}
// Load template files
private async loadTemplate(): Promise<string> {
const templatePath = path.join(this.templateDir, 'insight-template.html');
return await fs.readFile(templatePath, 'utf-8');
}
private async loadStyles(): Promise<string> {
const stylesPath = path.join(this.templateDir, 'styles', 'base.css');
return await fs.readFile(stylesPath, 'utf-8');
}
private async loadScripts(): Promise<string> {
const scriptsPath = path.join(this.templateDir, 'scripts', 'insight-app.js');
return await fs.readFile(scriptsPath, 'utf-8');
}
// Generate current timestamp
private generateTimestamp(): string {
return new Date().toLocaleString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
});
}
// Render the complete HTML file
async renderInsightHTML(insights: InsightData): Promise<string> {
const template = await this.loadTemplate();
const styles = await this.loadStyles();
const scripts = await this.loadScripts();
const generatedTime = this.generateTimestamp();
// Create empty content placeholder - content will be generated by JavaScript
const content = '<!-- Content will be generated by JavaScript -->';
// Replace all placeholders
let html = template;
html = html.replace('{{STYLES_PLACEHOLDER}}', styles);
html = html.replace('{{CONTENT_PLACEHOLDER}}', content);
html = html.replace('{{DATA_PLACEHOLDER}}', this.safeJsonStringify(insights));
html = html.replace('{{SCRIPTS_PLACEHOLDER}}', scripts);
html = html.replace('{{GENERATED_TIME}}', generatedTime);
return html;
}
// Create template data object
async createTemplateData(insights: InsightData): Promise<StaticInsightTemplateData> {
const styles = await this.loadStyles();
const scripts = await this.loadScripts();
const generatedTime = this.generateTimestamp();
return {
styles,
content: '<!-- Content will be generated by JavaScript -->',
data: insights,
scripts,
generatedTime,
};
}
}