Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/cli/src/i18n/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -1459,4 +1459,9 @@ export default {
'{{region}}-Konfiguration erfolgreich aktualisiert. Modell auf "{{model}}" umgeschaltet.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Erfolgreich mit {{region}} authentifiziert. API-Schlüssel und Modellkonfigurationen wurden in settings.json gespeichert (gesichert).',
// ===========================================================================
// Insight Report - Language Support
// ===========================================================================
'Generating insights in {{language}}...':
'Generiere Einblicke auf {{language}}...',
};
6 changes: 6 additions & 0 deletions packages/cli/src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1448,4 +1448,10 @@ export default {
'{{region}} configuration updated successfully. Model switched to "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).',

// ============================================================================
// Insight Report - Language Support
// ============================================================================
'Generating insights in {{language}}...':
'Generating insights in {{language}}...',
};
6 changes: 6 additions & 0 deletions packages/cli/src/i18n/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -966,4 +966,10 @@ export default {
'{{region}} の設定が正常に更新されました。モデルが "{{model}}" に切り替わりました。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'{{region}} での認証に成功しました。APIキーとモデル設定が settings.json に保存されました(バックアップ済み)。',

// ============================================================================
// Insight Report - Language Support
// ============================================================================
'Generating insights in {{language}}...':
'{{language}} でインサイトを生成中...',
};
5 changes: 5 additions & 0 deletions packages/cli/src/i18n/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -1453,4 +1453,9 @@ export default {
'Configuração do {{region}} atualizada com sucesso. Modelo alterado para "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Autenticado com sucesso com {{region}}. Chave de API e configurações de modelo salvas em settings.json (com backup).',
// ===========================================================================
// Insight Report - Language Support
// ===========================================================================
'Generating insights in {{language}}...':
'Gerando insights em {{language}}...',
};
5 changes: 5 additions & 0 deletions packages/cli/src/i18n/locales/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -1463,4 +1463,9 @@ export default {
'Конфигурация {{region}} успешно обновлена. Модель переключена на "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Успешная аутентификация с {{region}}. API-ключ и конфигурации моделей сохранены в settings.json (резервная копия создана).',
// ===========================================================================
// Insight Report - Language Support
// ===========================================================================
'Generating insights in {{language}}...':
'Генерация инсайтов на {{language}}...',
};
6 changes: 6 additions & 0 deletions packages/cli/src/i18n/locales/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -1281,4 +1281,10 @@ export default {
'{{region}} 配置更新成功。模型已切换至 "{{model}}"。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'成功通过 {{region}} 认证。API Key 和模型配置已保存至 settings.json(已备份)。',

// ============================================================================
// Insight Report - Language Support
// ============================================================================
'Generating insights in {{language}}...':
'正在生成 {{language}} 的洞察报告...',
};
22 changes: 19 additions & 3 deletions packages/cli/src/services/insight/generators/DataProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,27 @@ import {
type Config,
type ChatRecord,
} from '@qwen-code/qwen-code-core';
import type { SupportedLanguage } from '../../../i18n/index.js';

const logger = createDebugLogger('DataProcessor');

const CONCURRENCY_LIMIT = 4;

// Language display names for prompts
const LANGUAGE_DISPLAY_NAMES: Record<SupportedLanguage, string> = {
en: 'English',
zh: '中文 (Chinese)',
ja: '日本語 (Japanese)',
de: 'Deutsch (German)',
pt: 'Português (Portuguese)',
ru: 'Русский (Russian)',
};

export class DataProcessor {
constructor(private config: Config) {}
constructor(
private config: Config,
private language: SupportedLanguage = 'en',
) {}

// Helper function to format date as YYYY-MM-DD
private formatDate(date: Date): string {
Expand Down Expand Up @@ -193,7 +207,8 @@ export class DataProcessor {
};

const sessionText = this.formatRecordsForAnalysis(records);
const prompt = `${getInsightPrompt('analysis')}\n\nSESSION:\n${sessionText}`;
const languageInstruction = `\n\nIMPORTANT: Respond in ${LANGUAGE_DISPLAY_NAMES[this.language]}. All output text (brief_summary, underlying_goal, friction_detail) must be written in ${LANGUAGE_DISPLAY_NAMES[this.language]}.`;
const prompt = `${getInsightPrompt('analysis')}\n\nSESSION:\n${sessionText}${languageInstruction}`;

try {
const result = await this.config.getBaseLlmClient().generateJson({
Expand Down Expand Up @@ -384,12 +399,13 @@ export class DataProcessor {
logger.info('Generating qualitative insights...');

const commonData = this.prepareCommonPromptData(metrics, facets);
const languageInstruction = `\n\nIMPORTANT: Respond in ${LANGUAGE_DISPLAY_NAMES[this.language]}. All output text must be written in ${LANGUAGE_DISPLAY_NAMES[this.language]}.`;

const generate = async <T>(
promptTemplate: string,
schema: Record<string, unknown>,
): Promise<T> => {
const prompt = `${promptTemplate}\n\n${commonData}`;
const prompt = `${promptTemplate}${languageInstruction}\n\n${commonData}`;
try {
const result = await this.config.getBaseLlmClient().generateJson({
model: this.config.getModel(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@ import type {
} from '../types/StaticInsightTypes.js';

import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core';
import {
getCurrentLanguage,
type SupportedLanguage,
} from '../../../i18n/index.js';

const logger = createDebugLogger('StaticInsightGenerator');

export class StaticInsightGenerator {
private dataProcessor: DataProcessor;
private templateRenderer: TemplateRenderer;
private language: SupportedLanguage;

constructor(config: Config) {
this.dataProcessor = new DataProcessor(config);
this.templateRenderer = new TemplateRenderer();
this.language = getCurrentLanguage();
this.dataProcessor = new DataProcessor(config, this.language);
this.templateRenderer = new TemplateRenderer(this.language);
}

// Ensure the output directory exists
Expand Down
17 changes: 16 additions & 1 deletion packages/cli/src/services/insight/generators/TemplateRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,26 @@

import { INSIGHT_JS, INSIGHT_CSS } from '@qwen-code/web-templates';
import type { InsightData } from '../types/StaticInsightTypes.js';
import type { SupportedLanguage } from '../../../i18n/index.js';

// Language codes for HTML lang attribute
const HTML_LANG_CODES: Record<SupportedLanguage, string> = {
en: 'en',
zh: 'zh-CN',
ja: 'ja',
de: 'de',
pt: 'pt-BR',
ru: 'ru',
};

export class TemplateRenderer {
constructor(private language: SupportedLanguage = 'en') {}

// Render the complete HTML file
async renderInsightHTML(insights: InsightData): Promise<string> {
const htmlLang = HTML_LANG_CODES[this.language];
const html = `<!doctype html>
<html lang="en">
<html lang="${htmlLang}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand All @@ -38,6 +52,7 @@ export class TemplateRenderer {
<!-- Application Data -->
<script>
window.INSIGHT_DATA = ${JSON.stringify(insights)};
window.INSIGHT_LANGUAGE = ${JSON.stringify(this.language)};
</script>

<!-- App Script -->
Expand Down
19 changes: 18 additions & 1 deletion packages/cli/src/ui/commands/insightCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import type { CommandContext, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { MessageType } from '../types.js';
import type { HistoryItemInsightProgress } from '../types.js';
import { t } from '../../i18n/index.js';
import {
t,
getCurrentLanguage,
getLanguageNameFromLocale,
} from '../../i18n/index.js';
import { join } from 'path';
import os from 'os';
import { StaticInsightGenerator } from '../../services/insight/generators/StaticInsightGenerator.js';
Expand All @@ -29,6 +33,19 @@ export const insightCommand: SlashCommand = {
try {
context.ui.setDebugMessage(t('Generating insights...'));

// Get current language and display a message
const currentLang = getCurrentLanguage();
const langName = getLanguageNameFromLocale(currentLang);
context.ui.addItem(
{
type: MessageType.INFO,
text: t('Generating insights in {{language}}...', {
language: langName,
}),
},
Date.now(),
);

const projectsDir = join(os.homedir(), '.qwen', 'projects');
if (!context.services.config) {
throw new Error('Config service is not available');
Expand Down
20 changes: 13 additions & 7 deletions packages/web-templates/src/insight/src/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react';
import type { InsightData } from './types';
import { t } from './i18n';

// Get current language from window or default to 'en'
const currentLang =
(typeof window !== 'undefined' && window.INSIGHT_LANGUAGE) || 'en';
const tr = (key: string) => t(key, currentLang);

// Header Component
export function Header({
Expand All @@ -15,11 +21,11 @@ export function Header({
return (
<header className="mb-8 space-y-3 text-center">
<h1 className="text-3xl font-semibold text-slate-900 md:text-4xl">
Qwen Code Insights
{tr('Qwen Code Insights')}
</h1>
<p className="text-sm text-slate-600">
{totalMessages
? `${totalMessages} messages across ${totalSessions} sessions`
? `${totalMessages} ${tr('messages across')} ${totalSessions} ${tr('sessions')}`
: 'Your personalized coding journey and patterns'}
{dateRangeStr && ` | ${dateRangeStr}`}
</p>
Expand Down Expand Up @@ -54,25 +60,25 @@ export function StatsRow({ data }: { data: InsightData }) {
<div className="stats-row">
<div className="stat">
<div className="stat-value">{totalMessages}</div>
<div className="stat-label">Messages</div>
<div className="stat-label">{tr('Messages')}</div>
</div>
<div className="stat">
<div className="stat-value">
+{totalLinesAdded}/-{totalLinesRemoved}
</div>
<div className="stat-label">Lines</div>
<div className="stat-label">{tr('Lines')}</div>
</div>
<div className="stat">
<div className="stat-value">{totalFiles}</div>
<div className="stat-label">Files</div>
<div className="stat-label">{tr('Files')}</div>
</div>
<div className="stat">
<div className="stat-value">{daysSpan}</div>
<div className="stat-label">Days</div>
<div className="stat-label">{tr('Days')}</div>
</div>
<div className="stat">
<div className="stat-value">{msgsPerDay}</div>
<div className="stat-label">Msgs/Day</div>
<div className="stat-label">{tr('Msgs/Day')}</div>
</div>
</div>
);
Expand Down
Loading
Loading