lumencode 0.4.3
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.
- package/README.md +271 -0
- package/data/pricing.json +3708 -0
- package/index.js +266 -0
- package/lib/aggregate.js +626 -0
- package/lib/attribution.js +137 -0
- package/lib/blocks.js +86 -0
- package/lib/cache.js +18 -0
- package/lib/config.js +91 -0
- package/lib/git.js +1106 -0
- package/lib/models/usage-record.js +46 -0
- package/lib/parser.js +160 -0
- package/lib/parsers/base.js +67 -0
- package/lib/parsers/claude.js +316 -0
- package/lib/parsers/codex.js +316 -0
- package/lib/parsers/index.js +151 -0
- package/lib/parsers/opencode.js +216 -0
- package/lib/pricing-loader.js +287 -0
- package/lib/record-utils.js +35 -0
- package/lib/report.js +1446 -0
- package/lib/scenario.js +183 -0
- package/lib/server.js +412 -0
- package/lib/table.js +67 -0
- package/package.json +44 -0
- package/public/api.js +109 -0
- package/public/app.js +647 -0
- package/public/charts.js +197 -0
- package/public/config.js +141 -0
- package/public/export.js +282 -0
- package/public/fonts/inter-0.woff2 +0 -0
- package/public/fonts/inter-1.woff2 +0 -0
- package/public/fonts/inter-2.woff2 +0 -0
- package/public/fonts/inter-3.woff2 +0 -0
- package/public/fonts/inter.css +28 -0
- package/public/git-insights.js +123 -0
- package/public/index.html +347 -0
- package/public/style.css +1864 -0
- package/public/ui-state.js +103 -0
- package/public/utils.js +71 -0
- package/public/vendor/alpine.min.js +5 -0
- package/public/vendor/chart.umd.min.js +20 -0
- package/public/work-report.js +118 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ID } from './config.js';
|
|
2
|
+
import { esc, fmt, destroyChart } from './utils.js';
|
|
3
|
+
import { renderCommitTypeChart } from './charts.js';
|
|
4
|
+
|
|
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
|
+
}
|
|
66
|
+
|
|
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);
|
|
84
|
+
const typeEntries = gitStats.commitTypes
|
|
85
|
+
? Object.entries(gitStats.commitTypes).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1])
|
|
86
|
+
: [];
|
|
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) {
|
|
97
|
+
renderCommitTypeChart(typeEntries);
|
|
98
|
+
} else {
|
|
99
|
+
destroyChart('commitTypeChart');
|
|
100
|
+
}
|
|
101
|
+
|
|
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
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>LumenCode</title>
|
|
7
|
+
<link rel="stylesheet" href="/fonts/inter.css">
|
|
8
|
+
<script src="/vendor/chart.umd.min.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/style.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="toast" class="toast" style="display:none;"></div>
|
|
13
|
+
<nav class="top-nav">
|
|
14
|
+
<div class="nav-inner">
|
|
15
|
+
<div class="logo">LumenCode</div>
|
|
16
|
+
<div class="nav-controls">
|
|
17
|
+
<div class="nav-pill-group">
|
|
18
|
+
<button class="category-tab active" data-period="daily">日报</button>
|
|
19
|
+
<button class="category-tab" data-period="weekly">周报</button>
|
|
20
|
+
<button class="category-tab" data-period="monthly">月报</button>
|
|
21
|
+
</div>
|
|
22
|
+
<div style="display:flex;align-items:center;gap:4px;">
|
|
23
|
+
<button id="prevDate" class="icon-btn" title="上一个" aria-label="上一个日期">
|
|
24
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
25
|
+
</button>
|
|
26
|
+
<label for="dateInput" class="sr-only">选择日期</label>
|
|
27
|
+
<input type="date" id="dateInput" class="text-input">
|
|
28
|
+
<button id="nextDate" class="icon-btn" title="下一个" aria-label="下一个日期">
|
|
29
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
<button id="themeBtn" class="icon-btn" title="切换暗色模式">
|
|
33
|
+
<svg id="moonIcon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
34
|
+
<svg id="sunIcon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
35
|
+
</button>
|
|
36
|
+
<button id="settingsBtn" class="icon-btn" title="设置">
|
|
37
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</nav>
|
|
42
|
+
|
|
43
|
+
<div class="app-layout">
|
|
44
|
+
<aside class="tool-sidebar" :class="{ collapsed: collapsed }" x-data="toolTabs()" x-init="init()" x-show="availableTools.length > 0">
|
|
45
|
+
<div class="tool-sidebar-header">
|
|
46
|
+
<span class="tool-sidebar-title" x-show="!collapsed">数据源</span>
|
|
47
|
+
<button class="tool-sidebar-toggle" @click="toggleCollapse()" :title="collapsed ? '展开' : '收起'">
|
|
48
|
+
<svg x-show="!collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg>
|
|
49
|
+
<svg x-show="collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
<nav class="tool-nav">
|
|
53
|
+
<button class="tool-nav-item" :class="{ active: activeTool === 'all' }" @click="setTool('all')" title="汇总">
|
|
54
|
+
<span class="tool-dot tool-dot-all"></span>
|
|
55
|
+
<span class="tool-nav-label" x-show="!collapsed">汇总</span>
|
|
56
|
+
</button>
|
|
57
|
+
<template x-for="tool in availableTools" :key="tool.name">
|
|
58
|
+
<button
|
|
59
|
+
class="tool-nav-item"
|
|
60
|
+
:class="{ active: activeTool === tool.name, disabled: !tool.detected && !tool.enabled }"
|
|
61
|
+
:disabled="!tool.detected && !tool.enabled"
|
|
62
|
+
@click="setTool(tool.name)"
|
|
63
|
+
:title="tool.displayName"
|
|
64
|
+
>
|
|
65
|
+
<span class="tool-dot" :class="'tool-dot-' + tool.name"></span>
|
|
66
|
+
<span class="tool-nav-text" x-show="!collapsed">
|
|
67
|
+
<span class="tool-nav-label" x-text="tool.displayName"></span>
|
|
68
|
+
<span class="tool-nav-version" x-show="tool.version" x-text="'v' + tool.version"></span>
|
|
69
|
+
</span>
|
|
70
|
+
</button>
|
|
71
|
+
</template>
|
|
72
|
+
</nav>
|
|
73
|
+
<!-- 添加数据源功能暂未实现,先隐藏
|
|
74
|
+
<div class="tool-sidebar-footer">
|
|
75
|
+
<button class="tool-nav-add" @click="showAddTool = true" title="添加数据源">
|
|
76
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
77
|
+
<span class="tool-nav-label" x-show="!collapsed">添加</span>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
-->
|
|
81
|
+
</aside>
|
|
82
|
+
|
|
83
|
+
<main class="container" x-data="app()">
|
|
84
|
+
<section class="hero-band">
|
|
85
|
+
<div class="hero-row">
|
|
86
|
+
<div>
|
|
87
|
+
<h1 class="display-lg" id="reportTitle">Claude Code 使用日报</h1>
|
|
88
|
+
<p class="body-md muted" id="reportDate">加载中...</p>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="hero-actions" style="display:flex;gap:8px;">
|
|
91
|
+
<button id="exportCsvBtn" class="btn-outline">导出 CSV</button>
|
|
92
|
+
<button id="exportJsonBtn" class="btn-outline">导出 JSON</button>
|
|
93
|
+
<button id="exportHtmlBtn" class="btn-outline">保存 HTML</button>
|
|
94
|
+
<button id="printBtn" class="btn-outline">打印/PDF</button>
|
|
95
|
+
<button id="workReportBtn" class="btn-outline">工作汇报</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<section class="stats-grid" id="statsGrid">
|
|
101
|
+
<div class="feature-card">
|
|
102
|
+
<div class="card-label">独立会话</div>
|
|
103
|
+
<div class="card-value-row"><div class="card-value" id="statSessions">-</div><div class="card-trend" id="trendSessions"></div></div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="feature-card">
|
|
106
|
+
<div class="card-label">交互轮次</div>
|
|
107
|
+
<div class="card-value-row"><div class="card-value" id="statRequests">-</div><div class="card-trend" id="trendRequests"></div></div>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="feature-card">
|
|
110
|
+
<div class="card-label">覆盖项目</div>
|
|
111
|
+
<div class="card-value-row"><div class="card-value" id="statProjects">-</div><div class="card-trend" id="trendProjects"></div></div>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="feature-card">
|
|
114
|
+
<div class="card-label">Token 消耗</div>
|
|
115
|
+
<div class="card-value-row"><div class="card-value" id="statTokens">-</div><div class="card-trend" id="trendTokens"></div></div>
|
|
116
|
+
<div class="card-sub" id="statTokenBreakdown"></div>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="feature-card">
|
|
119
|
+
<div class="card-label">预估费用</div>
|
|
120
|
+
<div class="card-value-row"><div class="card-value" id="statCost">-</div><div class="card-trend" id="trendCost"></div></div>
|
|
121
|
+
<div class="card-sub" id="statCostModel"></div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<section class="git-section" id="gitSection" style="display:none;">
|
|
126
|
+
<div class="cta-band-light">
|
|
127
|
+
<h3 class="title-lg">Git 代码产出</h3>
|
|
128
|
+
<div class="git-stats" id="gitStats"></div>
|
|
129
|
+
<div class="git-ai-stats" id="gitAiStats"></div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="chart-row git-insights-row" id="gitInsightsRow" style="display:none;">
|
|
132
|
+
<div class="chart-card">
|
|
133
|
+
<h3 class="title-md">提交类型分布</h3>
|
|
134
|
+
<div class="chart-wrap"><canvas id="commitTypeChart"></canvas></div>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="chart-card">
|
|
137
|
+
<h3 class="title-md">文件热点 Top 10</h3>
|
|
138
|
+
<div id="fileHotspotsTable" class="hotspots-table"></div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<section id="analyticsSection">
|
|
144
|
+
<div class="section-header">
|
|
145
|
+
<h2 class="title-md">数据分析</h2>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="no-data-hint" id="noDataHint" style="display:none;">
|
|
148
|
+
<p>该时间段暂无使用数据</p>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="charts-dashboard" id="chartsDashboard">
|
|
151
|
+
<section class="charts-section">
|
|
152
|
+
<div class="chart-row">
|
|
153
|
+
<div class="chart-card">
|
|
154
|
+
<h3 class="title-md">工作类型分布</h3>
|
|
155
|
+
<div class="chart-wrap"><canvas id="scenarioChart"></canvas></div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="chart-card">
|
|
158
|
+
<h3 class="title-md">模型使用分布</h3>
|
|
159
|
+
<div class="chart-wrap"><canvas id="modelChart"></canvas></div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="chart-row">
|
|
163
|
+
<div class="chart-card">
|
|
164
|
+
<h3 class="title-md">项目使用分布</h3>
|
|
165
|
+
<div class="chart-wrap"><canvas id="projectChart"></canvas></div>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="chart-card">
|
|
168
|
+
<h3 class="title-md">工具调用排行</h3>
|
|
169
|
+
<div class="chart-wrap"><canvas id="toolChart"></canvas></div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
<section class="trend-section" id="trendSection" style="display:none;">
|
|
174
|
+
<div class="chart-card trend-card">
|
|
175
|
+
<h3 class="title-md">使用趋势</h3>
|
|
176
|
+
<div class="chart-wrap chart-wrap-tall"><canvas id="trendChart"></canvas></div>
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
<section class="trend-section" id="cacheSection" style="display:none;">
|
|
180
|
+
<div class="chart-card">
|
|
181
|
+
<h3 class="title-md">缓存效率</h3>
|
|
182
|
+
<div class="chart-wrap"><canvas id="cacheChart"></canvas></div>
|
|
183
|
+
</div>
|
|
184
|
+
</section>
|
|
185
|
+
<section class="trend-section" id="modelCostSection" style="display:none;">
|
|
186
|
+
<div class="chart-card">
|
|
187
|
+
<h3 class="title-md">模型费用分布</h3>
|
|
188
|
+
<div class="chart-wrap chart-wrap-tall"><canvas id="modelCostChart"></canvas></div>
|
|
189
|
+
</div>
|
|
190
|
+
</section>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
|
|
194
|
+
<section class="welcome-page" id="welcomePage" style="display:none;">
|
|
195
|
+
<div class="welcome-content">
|
|
196
|
+
<div class="welcome-brand">
|
|
197
|
+
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-bottom:16px;">
|
|
198
|
+
<rect x="8" y="12" width="32" height="24" rx="6" stroke="#111111" stroke-width="2.5" fill="none"/>
|
|
199
|
+
<path d="M16 22L22 28L32 18" stroke="#111111" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
200
|
+
</svg>
|
|
201
|
+
<h1 class="welcome-title">欢迎使用 LumenCode</h1>
|
|
202
|
+
<p class="welcome-subtitle">AI 编码助手使用统计与代码产出分析工具</p>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="welcome-features">
|
|
206
|
+
<div class="feature-item">
|
|
207
|
+
<svg class="feature-icon-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
208
|
+
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
209
|
+
<path d="M3 9h18M9 21V9"/>
|
|
210
|
+
</svg>
|
|
211
|
+
<div class="feature-text">
|
|
212
|
+
<h4>Token 消耗追踪</h4>
|
|
213
|
+
<p>按日/周/月统计输入、输出、缓存命中</p>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="feature-item">
|
|
217
|
+
<svg class="feature-icon-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
218
|
+
<circle cx="12" cy="12" r="9"/>
|
|
219
|
+
<path d="M12 6v6l4 2"/>
|
|
220
|
+
</svg>
|
|
221
|
+
<div class="feature-text">
|
|
222
|
+
<h4>费用估算</h4>
|
|
223
|
+
<p>基于模型定价自动计算成本</p>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="feature-item">
|
|
227
|
+
<svg class="feature-icon-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
228
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
229
|
+
<path d="M2 17l10 5 10-5"/>
|
|
230
|
+
<path d="M2 12l10 5 10-5"/>
|
|
231
|
+
</svg>
|
|
232
|
+
<div class="feature-text">
|
|
233
|
+
<h4>AI 贡献度</h4>
|
|
234
|
+
<p>关联 Git 提交,量化 AI 辅助占比</p>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div class="welcome-steps">
|
|
240
|
+
<div class="step-card">
|
|
241
|
+
<div class="step-header">
|
|
242
|
+
<span class="step-number">1</span>
|
|
243
|
+
<h3>选择 Claude 日志目录</h3>
|
|
244
|
+
</div>
|
|
245
|
+
<p class="step-desc">Claude Code 会话日志的存放路径,通常是 <code>~/.claude</code></p>
|
|
246
|
+
<label for="welcomeClaudeDir" class="sr-only">Claude 日志目录</label>
|
|
247
|
+
<input type="text" class="welcome-input" id="welcomeClaudeDir" placeholder="例如: C:/Users/xxx/.claude" />
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="step-card">
|
|
251
|
+
<div class="step-header">
|
|
252
|
+
<span class="step-number">2</span>
|
|
253
|
+
<h3>选择项目仓库(可选)</h3>
|
|
254
|
+
</div>
|
|
255
|
+
<p class="step-desc">用于关联 Git 代码产出统计,多个仓库用逗号分隔</p>
|
|
256
|
+
<label for="welcomeRepos" class="sr-only">项目仓库路径</label>
|
|
257
|
+
<input type="text" class="welcome-input" id="welcomeRepos" placeholder="例如: D:/project1, D:/project2" />
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div class="welcome-actions">
|
|
262
|
+
<button class="btn-primary welcome-start-btn" id="welcomeStartBtn">开始使用</button>
|
|
263
|
+
<p class="welcome-hint" id="welcomeHint"></p>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</section>
|
|
267
|
+
|
|
268
|
+
<section class="work-report-section" id="workReportSection" style="display:none;">
|
|
269
|
+
<div class="chart-card">
|
|
270
|
+
<div class="work-report-header">
|
|
271
|
+
<h3 class="title-md">工作汇报</h3>
|
|
272
|
+
<div class="work-report-actions">
|
|
273
|
+
<div class="level-tab-group">
|
|
274
|
+
<button class="level-tab active" data-level="detailed">详报</button>
|
|
275
|
+
<button class="level-tab" data-level="brief">简报</button>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="platform-pill-group">
|
|
278
|
+
<button class="platform-tab active" data-platform="default">标准</button>
|
|
279
|
+
<button class="platform-tab" data-platform="feishu">飞书</button>
|
|
280
|
+
<button class="platform-tab" data-platform="dingtalk">钉钉</button>
|
|
281
|
+
</div>
|
|
282
|
+
<span class="action-divider"></span>
|
|
283
|
+
<button id="copyWorkReport" class="btn-secondary">复制</button>
|
|
284
|
+
<button id="downloadMdBtn" class="btn-secondary">下载 .md</button>
|
|
285
|
+
<button id="backToReport" class="btn-primary">返回</button>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
<div id="workReportContent" class="work-report-content"></div>
|
|
289
|
+
</div>
|
|
290
|
+
</section>
|
|
291
|
+
<!-- Drill-down Modal -->
|
|
292
|
+
<div id="drillModal" class="modal" style="display:none;">
|
|
293
|
+
<div class="modal-backdrop"></div>
|
|
294
|
+
<div class="modal-panel" style="max-width:640px;">
|
|
295
|
+
<div class="modal-header">
|
|
296
|
+
<h3 class="title-md" id="drillTitle">明细</h3>
|
|
297
|
+
<button id="closeDrill" class="icon-btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="modal-body" id="drillBody"></div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</main>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<!-- Settings Modal -->
|
|
306
|
+
<div id="settingsModal" class="modal" style="display:none;">
|
|
307
|
+
<div class="modal-backdrop"></div>
|
|
308
|
+
<div class="modal-panel">
|
|
309
|
+
<div class="modal-header">
|
|
310
|
+
<h3 class="title-md">配置</h3>
|
|
311
|
+
<button id="closeSettings" class="icon-btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="modal-body">
|
|
314
|
+
<div class="form-group">
|
|
315
|
+
<label class="form-label">Claude 日志目录</label>
|
|
316
|
+
<input type="text" id="cfgClaudeDir" class="text-input form-input">
|
|
317
|
+
</div>
|
|
318
|
+
<div class="form-group">
|
|
319
|
+
<label class="form-label">本地项目路径(每行一个)</label>
|
|
320
|
+
<textarea id="cfgRepos" class="text-input form-textarea" rows="3"></textarea>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="form-group">
|
|
323
|
+
<label class="form-label">排除项目(每行一个)</label>
|
|
324
|
+
<textarea id="cfgExclude" class="text-input form-textarea" rows="3"></textarea>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="form-group">
|
|
327
|
+
<label class="form-label">场景关键词</label>
|
|
328
|
+
<textarea id="cfgKeywords" class="text-input form-textarea" rows="5"></textarea>
|
|
329
|
+
<p class="form-hint">JSON 格式,如 { "coding": ["实现", "开发"] }</p>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="modal-footer">
|
|
333
|
+
<button id="saveSettings" class="btn-primary">保存</button>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<footer class="footer">
|
|
339
|
+
<div class="footer-inner">
|
|
340
|
+
<p>LumenCode · AI 编码助手使用统计</p>
|
|
341
|
+
<p class="muted-soft">数据来自本地日志,不上传至任何服务器</p>
|
|
342
|
+
</div>
|
|
343
|
+
</footer>
|
|
344
|
+
|
|
345
|
+
<script type="module" src="/app.js"></script>
|
|
346
|
+
</body>
|
|
347
|
+
</html>
|