mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-04 22:51:08 +00:00
feat(insight): Refactor code structure for improved readability and maintainability
This commit is contained in:
parent
2edce464ae
commit
e66c203cb0
18 changed files with 423 additions and 229 deletions
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { read as readJsonlFile } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
read as readJsonlFile,
|
||||
createDebugLogger,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import pLimit from 'p-limit';
|
||||
import type { Config, ChatRecord } from '@qwen-code/qwen-code-core';
|
||||
import type {
|
||||
|
|
@ -40,6 +43,8 @@ import {
|
|||
ANALYSIS_PROMPT,
|
||||
} from '../prompts/InsightPrompts.js';
|
||||
|
||||
const logger = createDebugLogger('DataProcessor');
|
||||
|
||||
export class DataProcessor {
|
||||
constructor(private config: Config) {}
|
||||
|
||||
|
|
@ -194,7 +199,7 @@ export class DataProcessor {
|
|||
session_id: records[0].sessionId,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Failed to analyze session ${records[0]?.sessionId}:`,
|
||||
error,
|
||||
);
|
||||
|
|
@ -362,7 +367,7 @@ export class DataProcessor {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
console.log('Generating qualitative insights...');
|
||||
logger.info('Generating qualitative insights...');
|
||||
|
||||
const commonData = this.prepareCommonPromptData(metrics, facets);
|
||||
|
||||
|
|
@ -380,7 +385,7 @@ export class DataProcessor {
|
|||
});
|
||||
return result as T;
|
||||
} catch (error) {
|
||||
console.error('Failed to generate insight:', error);
|
||||
logger.error('Failed to generate insight:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -620,39 +625,6 @@ export class DataProcessor {
|
|||
),
|
||||
]);
|
||||
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ impressiveWorkflows:',
|
||||
impressiveWorkflows,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ atAGlance:',
|
||||
atAGlance,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ interactionStyle:',
|
||||
interactionStyle,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ improvements:',
|
||||
improvements,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ memorableMoment:',
|
||||
memorableMoment,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ frictionPoints:',
|
||||
frictionPoints,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ futureOpportunities:',
|
||||
futureOpportunities,
|
||||
);
|
||||
console.log(
|
||||
'🚀 ~ DataProcessor ~ generateQualitativeInsights ~ projectAreas:',
|
||||
projectAreas,
|
||||
);
|
||||
|
||||
return {
|
||||
impressiveWorkflows,
|
||||
projectAreas,
|
||||
|
|
@ -664,7 +636,7 @@ export class DataProcessor {
|
|||
atAGlance,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error generating qualitative insights:', e);
|
||||
logger.error('Error generating qualitative insights:', e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
@ -783,12 +755,12 @@ None captured`;
|
|||
const fileStats = await fs.stat(filePath);
|
||||
allChatFiles.push({ path: filePath, mtime: fileStats.mtimeMs });
|
||||
} catch (e) {
|
||||
console.error(`Failed to stat file ${filePath}:`, e);
|
||||
logger.error(`Failed to stat file ${filePath}:`, e);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
console.log(
|
||||
logger.error(
|
||||
`Error reading chats directory for project ${projectDir}: ${error}`,
|
||||
);
|
||||
}
|
||||
|
|
@ -800,9 +772,9 @@ None captured`;
|
|||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
// Base directory doesn't exist, return empty
|
||||
console.log(`Base directory does not exist: ${baseDir}`);
|
||||
logger.info(`Base directory does not exist: ${baseDir}`);
|
||||
} else {
|
||||
console.log(`Error reading base directory: ${error}`);
|
||||
logger.error(`Error reading base directory: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -898,7 +870,7 @@ None captured`;
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Failed to process metrics for file ${fileInfo.path}:`,
|
||||
error,
|
||||
);
|
||||
|
|
@ -994,10 +966,10 @@ None captured`;
|
|||
.sort((a, b) => b.mtime - a.mtime)
|
||||
.slice(0, 50);
|
||||
|
||||
console.log(`Analyzing ${recentFiles.length} recent sessions with LLM...`);
|
||||
logger.info(`Analyzing ${recentFiles.length} recent sessions with LLM...`);
|
||||
|
||||
// Create a limit function with concurrency of 4 to avoid 429 errors
|
||||
const limit = pLimit(4);
|
||||
const limit = pLimit(2);
|
||||
|
||||
let completed = 0;
|
||||
const total = recentFiles.length;
|
||||
|
|
@ -1036,7 +1008,7 @@ None captured`;
|
|||
} catch (readError) {
|
||||
// File doesn't exist or is invalid, proceed to analyze
|
||||
if ((readError as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
console.warn(
|
||||
logger.warn(
|
||||
`Failed to read existing facet for ${sessionId}, regenerating:`,
|
||||
readError,
|
||||
);
|
||||
|
|
@ -1059,7 +1031,7 @@ None captured`;
|
|||
'utf-8',
|
||||
);
|
||||
} catch (writeError) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Failed to write facet file for session ${facet.session_id}:`,
|
||||
writeError,
|
||||
);
|
||||
|
|
@ -1074,7 +1046,7 @@ None captured`;
|
|||
|
||||
return facet;
|
||||
} catch (e) {
|
||||
console.error(`Error analyzing session file ${fileInfo.path}:`, e);
|
||||
logger.error(`Error analyzing session file ${fileInfo.path}:`, e);
|
||||
completed++;
|
||||
if (onProgress) {
|
||||
const percent = 20 + Math.round((completed / total) * 60);
|
||||
|
|
|
|||
|
|
@ -37,33 +37,25 @@ export class StaticInsightGenerator {
|
|||
baseDir: string,
|
||||
onProgress?: InsightProgressCallback,
|
||||
): Promise<string> {
|
||||
try {
|
||||
// Ensure output directory exists
|
||||
const outputDir = await this.ensureOutputDirectory();
|
||||
const facetsDir = path.join(outputDir, 'facets');
|
||||
await fs.mkdir(facetsDir, { recursive: true });
|
||||
// Ensure output directory exists
|
||||
const outputDir = await this.ensureOutputDirectory();
|
||||
const facetsDir = path.join(outputDir, 'facets');
|
||||
await fs.mkdir(facetsDir, { recursive: true });
|
||||
|
||||
// Process data
|
||||
console.log('Processing insight data...');
|
||||
const insights: InsightData = await this.dataProcessor.generateInsights(
|
||||
baseDir,
|
||||
facetsDir,
|
||||
onProgress,
|
||||
);
|
||||
// Process data
|
||||
const insights: InsightData = await this.dataProcessor.generateInsights(
|
||||
baseDir,
|
||||
facetsDir,
|
||||
onProgress,
|
||||
);
|
||||
|
||||
// Render HTML
|
||||
console.log('Rendering HTML template...');
|
||||
const html = await this.templateRenderer.renderInsightHTML(insights);
|
||||
// Render HTML
|
||||
const html = await this.templateRenderer.renderInsightHTML(insights);
|
||||
|
||||
const outputPath = path.join(outputDir, 'insight.html');
|
||||
const outputPath = path.join(outputDir, 'insight.html');
|
||||
|
||||
// Write the HTML file
|
||||
console.log(`Writing HTML file to: ${outputPath}`);
|
||||
await fs.writeFile(outputPath, html, 'utf-8');
|
||||
return outputPath;
|
||||
} catch (error) {
|
||||
console.log(`Error generating static insight: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
// Write the HTML file
|
||||
await fs.writeFile(outputPath, html, 'utf-8');
|
||||
return outputPath;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,80 +4,48 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import { existsSync } from 'fs';
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { INSIGHT_JS, INSIGHT_CSS } from '../templates/insightTemplate.js';
|
||||
import type { InsightData } from '../types/StaticInsightTypes.js';
|
||||
|
||||
export class TemplateRenderer {
|
||||
private templateDir: string;
|
||||
|
||||
constructor() {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// In bundled version (dist/cli.js), __dirname is dist/, templates at dist/templates/
|
||||
// In development (dist/src/services/insight/generators/), templates at dist/src/services/insight/templates/
|
||||
const bundledTemplatePath = path.join(__dirname, 'templates');
|
||||
const devTemplatePath = path.join(__dirname, '..', 'templates');
|
||||
|
||||
// Try bundled path first (for production), fall back to dev path
|
||||
try {
|
||||
// Check if bundled templates exist
|
||||
if (existsSync(bundledTemplatePath)) {
|
||||
this.templateDir = bundledTemplatePath;
|
||||
} else {
|
||||
this.templateDir = devTemplatePath;
|
||||
}
|
||||
} catch {
|
||||
// If check fails, use dev path as fallback
|
||||
this.templateDir = devTemplatePath;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 componentsDir = path.join(this.templateDir, 'scripts', 'components');
|
||||
|
||||
const componentFiles = [
|
||||
'utils.js',
|
||||
'Header.js',
|
||||
'Qualitative.js',
|
||||
'Charts.js',
|
||||
'App.js',
|
||||
];
|
||||
|
||||
const scripts = await Promise.all(
|
||||
componentFiles.map((file) =>
|
||||
fs.readFile(path.join(componentsDir, file), 'utf-8'),
|
||||
),
|
||||
);
|
||||
|
||||
return scripts.join('\n\n');
|
||||
}
|
||||
|
||||
// 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 html = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Qwen Code Insights</title>
|
||||
<style>
|
||||
${INSIGHT_CSS}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="min-h-screen" id="container">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10 md:py-12">
|
||||
<div id="react-root"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Replace all placeholders
|
||||
let html = template;
|
||||
html = html.replace('{{STYLES_PLACEHOLDER}}', styles);
|
||||
html = html.replace('{{DATA_PLACEHOLDER}}', JSON.stringify(insights));
|
||||
html = html.replace('{{SCRIPTS_PLACEHOLDER}}', scripts);
|
||||
<!-- React CDN -->
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
|
||||
<!-- CDN Libraries -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
||||
|
||||
<!-- Application Data -->
|
||||
<script>
|
||||
window.INSIGHT_DATA = ${JSON.stringify(insights)};
|
||||
</script>
|
||||
|
||||
<!-- App Script -->
|
||||
<script>
|
||||
${INSIGHT_JS}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue