lumencode 0.4.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,123 +1,49 @@
1
- import { ID } from './config.js';
2
1
  import { esc, fmt, destroyChart } from './utils.js';
3
- import { renderCommitTypeChart } from './charts.js';
4
2
 
5
- export function renderGitInsights(gitStats, activeTool = 'all') {
6
- const aiStatsEl = document.getElementById(ID.GIT_AI_STATS);
7
- const ai = gitStats.aiContribution;
8
- if (!ai || gitStats.commits <= 0) { aiStatsEl.innerHTML = ''; return; }
9
-
10
- const commitPct = Math.round((ai.aiCommitRatio ?? (ai.aiCommits / gitStats.commits)) * 100);
11
- const linePct = Math.round(((ai.aiLineRatio ?? ai.aiRatio) || 0) * 100);
12
- const toolNames = { claude: 'Claude', codex: 'Codex', opencode: 'OpenCode' };
13
- const toolLabel = activeTool !== 'all' ? ((toolNames[activeTool] || activeTool) + ' ') : '';
14
-
15
- const totalLines = ai.totalLinesChanged || (ai.aiFileLinesAdded + ai.aiFileLinesDeleted + (ai.humanLinesChanged || 0)) || 1;
16
- const aiLinePct = Math.round((ai.aiLinesChanged / totalLines) * 100) || linePct;
17
-
18
- const toolTheme = activeTool !== 'all' ? `theme-${activeTool}` : 'theme-all';
19
-
20
- let summaryDesc = '';
21
- if (activeTool !== 'all') {
22
- summaryDesc = `${toolLabel}代码变更有 AI 参与`;
23
- } else if (gitStats.attributionSummary) {
24
- const s = gitStats.attributionSummary;
25
- const upperPct = Math.round(((s.confirmedAILines + s.probableAILines + s.possibleAILines) / (s.totalLinesChanged || 1)) * 100);
26
- summaryDesc = `代码变更有 AI 参与(可能上限 <strong>${upperPct}%</strong>)`;
27
- } else {
28
- summaryDesc = '代码变更有 AI 参与';
29
- }
30
-
31
- const summaryHtml = `
32
- <div class="ai-summary-card ${toolTheme}">
33
- <div class="ai-summary-left">
34
- <span class="ai-summary-pct">${aiLinePct}%</span>
35
- <span class="ai-summary-desc">${summaryDesc}</span>
36
- </div>
37
- <div class="ai-summary-right">
38
- <span class="ai-summary-commits">${ai.aiCommits}/${gitStats.commits} 提交使用 AI (${commitPct}%)</span>
39
- <div class="ai-summary-bar"><div class="ai-summary-bar-fill" style="width:${commitPct}%"></div></div>
40
- </div>
41
- </div>
42
- `;
43
-
44
- const tip = (text) => `<span class="metric-tip" data-tip="${text}">?</span>`;
45
- const kv = (value, label, tipText) =>
46
- `<div class="ai-kv-row"><span class="ai-kv-val">${value}</span><span class="ai-kv-lbl">${label}${tipText ? tip(tipText) : ''}</span></div>`;
47
-
48
- let metrics = [];
49
-
50
- if (activeTool !== 'all') {
51
- const total = ai.totalLinesChanged || 1;
52
- const confirmedPct = Math.round((ai.aiLinesChanged / total) * 100);
53
- const humanPct = 100 - confirmedPct;
54
- metrics.push(kv(`${confirmedPct}%`, `${toolLabel}确认 AI`, '该工具关联的 AI 提交代码行占总变更行比例'));
55
- metrics.push(kv(`${humanPct}%`, `${toolLabel}未归因`, '未关联到该工具 AI session 的代码行'));
56
- } else if (gitStats.attributionSummary) {
57
- const s = gitStats.attributionSummary;
58
- const total = s.totalLinesChanged || 1;
59
- const confirmedPct = Math.round((s.confirmedAILines / total) * 100);
60
- const upperPct = Math.round(((s.confirmedAILines + s.probableAILines + s.possibleAILines) / total) * 100);
61
- const unknownPct = Math.round((s.unknownLines / total) * 100);
62
- metrics.push(kv(`${confirmedPct}%`, '确认 AI 改动', '有明确 AI 签名或高置信 session 关联+文件交集的代码行占比'));
63
- metrics.push(kv(`${upperPct}%`, '可能 AI 上限', '确认+弱信号匹配的最大覆盖面,实际 AI 参与度在此区间内'));
64
- metrics.push(kv(`${unknownPct}%`, '未归因改动', '未能关联到任何 AI session 的代码行'));
65
- }
3
+ /* ── Git Insights (adapted for new design) ──
4
+ In the new design, AI contribution is rendered directly via Alpine.js
5
+ reactive data in app.js. This module is kept for backward compatibility
6
+ and provides data formatting utilities. ── */
66
7
 
67
- metrics.push(kv(`${linePct}%`, `${toolLabel}AI 代码改写`, 'AI 命中文件的新增+删除行占总变更行比例'));
68
- metrics.push(kv(`${ai.aiCommits}/${gitStats.commits}`, `${toolLabel}AI 提交 (${commitPct}%)`, '高/中置信度 AI 提交占总提交比'));
69
- metrics.push(kv(`${ai.highConfidenceCommits}/${ai.mediumConfidenceCommits}`, `${toolLabel}高/中置信`, '高=签名或Bash关联+交集;中=时间窗关联+交集'));
70
- metrics.push(kv(`+${fmt(ai.aiFileLinesAdded)}`, `${toolLabel}AI 新增行`, 'AI 提交中与 session 文件有交集的新增行'));
71
- metrics.push(kv(`-${fmt(ai.aiFileLinesDeleted)}`, `${toolLabel}AI 删除行`, 'AI 提交中与 session 文件有交集的删除行'));
72
-
73
- if (activeTool !== 'all' && gitStats.aiContributionByTool) {
74
- const globalAi = gitStats.aiContributionByTool;
75
- const allAiCommits = (globalAi.claude?.aiCommits || 0) + (globalAi.codex?.aiCommits || 0) + (globalAi.opencode?.aiCommits || 0) + (globalAi['generic-ai']?.aiCommits || 0);
76
- const globalPct = gitStats.commits > 0 ? Math.round((allAiCommits / gitStats.commits) * 100) : 0;
77
- metrics.push(kv(`${globalPct}%`, '全局 AI 提交率', '所有工具的 AI 归因提交数占总提交数比例'));
78
- }
79
-
80
- aiStatsEl.innerHTML = `${summaryHtml}<div class="ai-metrics-list">${metrics.join('')}</div>`;
81
-
82
- // 提交类型分布 + 文件热点
83
- const row = document.getElementById(ID.GIT_INSIGHTS_ROW);
8
+ export function renderGitInsights(gitStats, activeTool = 'all') {
9
+ /* No-op: new design renders git insights via Alpine reactive state */
10
+ if (!gitStats) return;
11
+ /* Render commit type chart if a container exists (legacy fallback) */
84
12
  const typeEntries = gitStats.commitTypes
85
13
  ? Object.entries(gitStats.commitTypes).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1])
86
14
  : [];
87
- const hotspots = gitStats.fileHotspots || [];
88
-
89
- if (typeEntries.length === 0 && hotspots.length === 0) {
90
- row.style.display = 'none';
91
- destroyChart('commitTypeChart');
92
- return;
93
- }
94
- row.style.display = '';
95
-
96
- if (typeEntries.length > 0) {
15
+ if (typeEntries.length > 0 && document.getElementById('commitTypeChart')) {
97
16
  renderCommitTypeChart(typeEntries);
98
- } else {
99
- destroyChart('commitTypeChart');
100
17
  }
18
+ }
101
19
 
102
- const hostEl = document.getElementById(ID.FILE_HOTSPOTS_TABLE);
103
- if (hotspots.length === 0) {
104
- hostEl.innerHTML = '<div class="hotspots-empty">无文件变更数据</div>';
105
- } else {
106
- const maxTouch = Math.max(...hotspots.map(h => h.touches));
107
- const truncate = (p) => p.length > 40 ? '...' + p.slice(-37) : p;
108
- hostEl.innerHTML = `
109
- <table class="hotspots-tbl">
110
- <thead><tr><th>文件</th><th class="num">触碰</th><th class="num">+行</th><th class="num">-行</th><th>热度</th></tr></thead>
111
- <tbody>${hotspots.map(h => {
112
- const pct = Math.max(8, Math.round((h.touches / maxTouch) * 100));
113
- return `<tr>
114
- <td class="hotspot-path" title="${esc(h.path)}">${esc(truncate(h.path))}</td>
115
- <td class="num">${h.touches}</td>
116
- <td class="num pos">+${fmt(h.added)}</td>
117
- <td class="num neg">-${fmt(h.deleted)}</td>
118
- <td><div class="hotspot-bar"><div class="hotspot-bar-fill" style="width:${pct}%"></div></div></td>
119
- </tr>`;
120
- }).join('')}</tbody>
121
- </table>`;
122
- }
20
+ function renderCommitTypeChart(typeEntries) {
21
+ destroyChart('commitTypeChart');
22
+ const canvas = document.getElementById('commitTypeChart');
23
+ if (!canvas) return;
24
+ const wrap = canvas.parentElement;
25
+ if (wrap) wrap.style.height = Math.max(180, typeEntries.length * 32 + 40) + 'px';
26
+ const labels = typeEntries.map(([k]) => k);
27
+ const data = typeEntries.map(([, v]) => v);
28
+ const colors = labels.map(k => COMMIT_TYPE_COLORS[k] || COMMIT_TYPE_COLORS.other);
29
+ const isDark = document.documentElement.classList.contains('dark');
30
+ const gridColor = isDark ? 'rgba(232,233,239,0.10)' : 'rgba(21,21,26,0.12)';
31
+ const tickColor = isDark ? 'rgba(232,233,239,0.55)' : 'rgba(21,21,26,0.55)';
32
+ const ctx = canvas.getContext('2d');
33
+ const instance = new Chart(ctx, {
34
+ type: 'bar',
35
+ data: { labels, datasets: [{ label: '提交数', data, backgroundColor: colors, borderRadius: 6, maxBarThickness: 20, barPercentage: 0.65, categoryPercentage: 0.85 }] },
36
+ options: {
37
+ responsive: true, maintainAspectRatio: false, indexAxis: 'y',
38
+ scales: { x: { grid: { color: gridColor }, ticks: { font: { family: 'JetBrains Mono', size: 11 }, color: tickColor, precision: 0 }, border: { display: false } }, y: { grid: { display: false }, ticks: { font: { family: 'JetBrains Mono', size: 12 }, color: tickColor }, border: { display: false } } },
39
+ plugins: { legend: { display: false } },
40
+ },
41
+ });
42
+ setChart('commitTypeChart', instance);
123
43
  }
44
+
45
+ const COMMIT_TYPE_COLORS = {
46
+ feat: '#8ab8a0', fix: '#c49090', refactor: '#a090c0', docs: '#90a8c8',
47
+ test: '#c8b880', chore: '#a8a8a8', perf: '#c890b0', style: '#80b8b8',
48
+ ci: '#c8a080', build: '#a8c880', revert: '#c49090', other: '#b8b8b8',
49
+ };