lumencode 0.4.3 → 1.0.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.
- package/README.md +54 -38
- package/index.js +79 -10
- package/lib/aggregate.js +40 -6
- package/lib/cache.js +8 -0
- package/lib/git.js +24 -2
- package/lib/report.js +14 -14
- package/lib/server.js +523 -412
- package/package.json +1 -1
- package/public/api.js +6 -0
- package/public/app.js +697 -535
- package/public/charts.js +278 -130
- package/public/config.js +21 -21
- package/public/git-insights.js +39 -113
- package/public/index.html +728 -347
- package/public/style.css +829 -1702
- package/public/ui-state.js +8 -67
- package/public/utils.js +10 -0
- package/public/work-report.js +1 -22
package/public/charts.js
CHANGED
|
@@ -1,30 +1,59 @@
|
|
|
1
|
-
import { COLORS, SCENARIO_COLORS, COMMIT_TYPE_COLORS
|
|
1
|
+
import { COLORS, SCENARIO_COLORS, COMMIT_TYPE_COLORS } from './config.js';
|
|
2
2
|
import { esc, fmt, fmtShort, destroyChart, setChart } from './utils.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export function
|
|
4
|
+
/* ── Work Type Pie (doughnut with inner radius) ── */
|
|
5
|
+
export function renderWorkTypePie(canvasId, entries) {
|
|
6
6
|
destroyChart(canvasId);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
7
|
+
const canvas = document.getElementById(canvasId);
|
|
8
|
+
if (!canvas) return;
|
|
9
|
+
const ctx = canvas.getContext('2d');
|
|
10
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
11
|
+
const ttBg = isDark ? '#1f222a' : '#f0eee7';
|
|
12
|
+
const ttFg = isDark ? '#e8e9ef' : '#15151a';
|
|
13
|
+
|
|
14
|
+
const labels = entries.map(([k]) => k);
|
|
15
|
+
const data = entries.map(([, v]) => v);
|
|
16
|
+
const colors = labels.map(k => SCENARIO_COLORS[k] || '#888');
|
|
17
|
+
|
|
18
|
+
const chart = new Chart(ctx, {
|
|
13
19
|
type: 'doughnut',
|
|
14
20
|
data: {
|
|
15
|
-
labels
|
|
16
|
-
datasets: [{
|
|
21
|
+
labels,
|
|
22
|
+
datasets: [{
|
|
23
|
+
data,
|
|
24
|
+
backgroundColor: colors,
|
|
25
|
+
borderWidth: 0,
|
|
26
|
+
hoverOffset: 4,
|
|
27
|
+
}],
|
|
17
28
|
},
|
|
18
29
|
options: {
|
|
19
|
-
responsive: true,
|
|
30
|
+
responsive: true,
|
|
31
|
+
maintainAspectRatio: false,
|
|
32
|
+
cutout: '65%',
|
|
20
33
|
plugins: {
|
|
21
|
-
legend: {
|
|
22
|
-
tooltip: {
|
|
34
|
+
legend: { display: false },
|
|
35
|
+
tooltip: {
|
|
36
|
+
backgroundColor: ttBg,
|
|
37
|
+
titleColor: ttFg,
|
|
38
|
+
bodyColor: ttFg,
|
|
39
|
+
borderColor: isDark ? 'rgba(232,233,239,0.12)' : 'rgba(21,21,26,0.12)',
|
|
40
|
+
borderWidth: 1,
|
|
41
|
+
borderWidth: 0,
|
|
42
|
+
cornerRadius: 0,
|
|
43
|
+
padding: 8,
|
|
44
|
+
titleFont: { family: 'JetBrains Mono', size: 11 },
|
|
45
|
+
bodyFont: { family: 'JetBrains Mono', size: 11 },
|
|
46
|
+
callbacks: {
|
|
47
|
+
label: (c) => {
|
|
48
|
+
const total = c.dataset.data.reduce((s, v) => s + v, 0);
|
|
49
|
+
return ` ${c.label}: ${c.raw} (${((c.raw / total) * 100).toFixed(1)}%)`;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
23
53
|
},
|
|
24
54
|
onClick: (evt, elements) => {
|
|
25
55
|
if (elements.length === 0) return;
|
|
26
|
-
const
|
|
27
|
-
const label = clickEntries[elements[0].index]?.[0];
|
|
56
|
+
const label = entries[elements[0].index]?.[0];
|
|
28
57
|
const scenarioKeyMap = { '编码': 'coding', '测试/QA': 'testing', '调试/排错': 'debugging', '文档': 'documentation', '阅读/研究': 'reading', '规划/设计': 'planning', '代码审查': 'review' };
|
|
29
58
|
const key = scenarioKeyMap[label];
|
|
30
59
|
if (!key) return;
|
|
@@ -32,166 +61,285 @@ export function renderDoughnut(canvasId, dataMap, label) {
|
|
|
32
61
|
},
|
|
33
62
|
},
|
|
34
63
|
});
|
|
35
|
-
setChart(canvasId,
|
|
36
|
-
return instance;
|
|
64
|
+
setChart(canvasId, chart);
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
export function
|
|
67
|
+
/* ── Project Bars (horizontal bar, minimal style) ── */
|
|
68
|
+
export function renderProjectBars(canvasId, entries) {
|
|
41
69
|
destroyChart(canvasId);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
type: 'bar',
|
|
45
|
-
data: { labels, datasets: [{ label: datasetLabel, data, backgroundColor: '#374151', borderRadius: 6, maxBarThickness: 20, barPercentage: 0.7 }] },
|
|
46
|
-
options: {
|
|
47
|
-
responsive: true, maintainAspectRatio: false, indexAxis: 'y',
|
|
48
|
-
scales: { x: { grid: { color: '#f3f4f6' }, ticks: { font: { family: 'Inter', size: 11 } } }, y: { grid: { display: false }, ticks: { font: { family: 'Inter', size: 12 } } } },
|
|
49
|
-
plugins: { legend: { display: false } },
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
setChart(canvasId, instance);
|
|
53
|
-
return instance;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Commit Type ──
|
|
57
|
-
export function renderCommitTypeChart(typeEntries) {
|
|
58
|
-
destroyChart('commitTypeChart');
|
|
59
|
-
const canvas = document.getElementById('commitTypeChart');
|
|
60
|
-
const wrap = canvas.parentElement;
|
|
61
|
-
wrap.style.height = Math.max(180, typeEntries.length * 32 + 40) + 'px';
|
|
62
|
-
const labels = typeEntries.map(([k]) => k);
|
|
63
|
-
const data = typeEntries.map(([, v]) => v);
|
|
64
|
-
const colors = labels.map(k => COMMIT_TYPE_COLORS[k] || COMMIT_TYPE_COLORS.other);
|
|
70
|
+
const canvas = document.getElementById(canvasId);
|
|
71
|
+
if (!canvas) return;
|
|
65
72
|
const ctx = canvas.getContext('2d');
|
|
66
|
-
const instance = new Chart(ctx, {
|
|
67
|
-
type: 'bar',
|
|
68
|
-
data: { labels, datasets: [{ label: '提交数', data, backgroundColor: colors, borderRadius: 6, maxBarThickness: 20, barPercentage: 0.65, categoryPercentage: 0.85 }] },
|
|
69
|
-
options: {
|
|
70
|
-
responsive: true, maintainAspectRatio: false, indexAxis: 'y',
|
|
71
|
-
scales: { x: { grid: { color: '#f3f4f6' }, ticks: { font: { family: 'Inter', size: 11 }, precision: 0 } }, y: { grid: { display: false }, ticks: { font: { family: 'Inter', size: 12 } } } },
|
|
72
|
-
plugins: { legend: { display: false } },
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
setChart('commitTypeChart', instance);
|
|
76
|
-
return instance;
|
|
77
|
-
}
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
type: 'line',
|
|
74
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
75
|
+
const gridColor = isDark ? 'rgba(232,233,239,0.06)' : 'rgba(21,21,26,0.06)';
|
|
76
|
+
const tickColor = isDark ? 'rgba(232,233,239,0.55)' : 'rgba(21,21,26,0.55)';
|
|
77
|
+
const barColor = isDark ? '#e8e9ef' : '#15151a';
|
|
78
|
+
const accentColor = isDark ? '#7480e8' : '#4a52a8';
|
|
79
|
+
const ttBg = isDark ? '#1f222a' : '#f0eee7';
|
|
80
|
+
const ttFg = isDark ? '#e8e9ef' : '#15151a';
|
|
81
|
+
|
|
82
|
+
const chart = new Chart(ctx, {
|
|
83
|
+
type: 'bar',
|
|
90
84
|
data: {
|
|
91
|
-
labels,
|
|
92
|
-
datasets: [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
labels: entries.map(([k]) => k.length > 20 ? '...' + k.slice(-17) : k),
|
|
86
|
+
datasets: [{
|
|
87
|
+
data: entries.map(([, v]) => v.requests),
|
|
88
|
+
backgroundColor: entries.map((_, i) => i === 0 ? accentColor : barColor),
|
|
89
|
+
borderRadius: 4,
|
|
90
|
+
maxBarThickness: 14,
|
|
91
|
+
barPercentage: 0.7,
|
|
92
|
+
}],
|
|
96
93
|
},
|
|
97
94
|
options: {
|
|
98
|
-
responsive: true,
|
|
95
|
+
responsive: true,
|
|
96
|
+
maintainAspectRatio: false,
|
|
97
|
+
indexAxis: 'y',
|
|
99
98
|
scales: {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
x: {
|
|
100
|
+
grid: { color: gridColor, drawBorder: false },
|
|
101
|
+
ticks: { font: { family: 'JetBrains Mono', size: 10 }, color: tickColor },
|
|
102
|
+
border: { display: false },
|
|
103
|
+
},
|
|
104
|
+
y: {
|
|
105
|
+
grid: { display: false },
|
|
106
|
+
ticks: { font: { family: 'JetBrains Mono', size: 11 }, color: tickColor },
|
|
107
|
+
border: { display: false },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
plugins: {
|
|
111
|
+
legend: { display: false },
|
|
112
|
+
tooltip: {
|
|
113
|
+
backgroundColor: ttBg,
|
|
114
|
+
titleColor: ttFg,
|
|
115
|
+
bodyColor: ttFg,
|
|
116
|
+
borderColor: isDark ? 'rgba(232,233,239,0.12)' : 'rgba(21,21,26,0.12)',
|
|
117
|
+
borderWidth: 1,
|
|
118
|
+
cornerRadius: 4,
|
|
119
|
+
padding: 8,
|
|
120
|
+
titleFont: { family: 'JetBrains Mono', size: 11 },
|
|
121
|
+
bodyFont: { family: 'JetBrains Mono', size: 11 },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
onClick: async (evt, elements) => {
|
|
125
|
+
if (elements.length === 0) return;
|
|
126
|
+
const project = entries[elements[0].index][0];
|
|
127
|
+
showDrill(esc(project), '<div class="drill-empty">加载中...</div>');
|
|
128
|
+
try {
|
|
129
|
+
const appEl = document.querySelector('[x-data]');
|
|
130
|
+
const app = appEl?._x_dataStack?.[0];
|
|
131
|
+
const params = { project, period: app?.period || 'daily', date: app?.currentDate || new Date().toISOString().slice(0, 10) };
|
|
132
|
+
if (app?.activeTool && app.activeTool !== 'all') params.tool = app.activeTool;
|
|
133
|
+
const { fetchSessions } = await import('./api.js');
|
|
134
|
+
const rows = await fetchSessions(params);
|
|
135
|
+
renderSessionDrill(project, rows);
|
|
136
|
+
} catch { showDrill(esc(project), '<div class="drill-empty">加载失败</div>'); }
|
|
103
137
|
},
|
|
104
|
-
plugins: { legend: { position: 'top', labels: { font: { family: 'Inter', size: 12 }, padding: 16 } } },
|
|
105
138
|
},
|
|
106
139
|
});
|
|
107
|
-
setChart(
|
|
108
|
-
return instance;
|
|
140
|
+
setChart(canvasId, chart);
|
|
109
141
|
}
|
|
110
142
|
|
|
111
|
-
|
|
112
|
-
export function
|
|
113
|
-
const total = cacheRead + inputTokens + cacheCreate;
|
|
114
|
-
if (total === 0) { destroyChart(canvasId); return null; }
|
|
115
|
-
|
|
143
|
+
/* ── Timeline Area Chart ── */
|
|
144
|
+
export function renderTimelineArea(canvasId, trendData) {
|
|
116
145
|
destroyChart(canvasId);
|
|
117
146
|
const canvas = document.getElementById(canvasId);
|
|
118
|
-
if (!canvas) return
|
|
147
|
+
if (!canvas) return;
|
|
119
148
|
const ctx = canvas.getContext('2d');
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
|
|
150
|
+
const dates = Object.keys(trendData.dailyStats || {}).sort();
|
|
151
|
+
if (dates.length === 0) return;
|
|
152
|
+
|
|
153
|
+
const sessions = dates.map(d => trendData.dailyStats[d].requests || 0);
|
|
154
|
+
const tokens = dates.map(d => ((trendData.dailyStats[d].inputTokens || 0) + (trendData.dailyStats[d].outputTokens || 0)) / 1_000_000);
|
|
155
|
+
const labels = dates.map(d => d.slice(5));
|
|
156
|
+
|
|
157
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
158
|
+
const gridColor = isDark ? 'rgba(232,233,239,0.06)' : 'rgba(21,21,26,0.06)';
|
|
159
|
+
const tickColor = isDark ? 'rgba(232,233,239,0.55)' : 'rgba(21,21,26,0.55)';
|
|
160
|
+
const sessionColor = isDark ? '#e8e9ef' : '#15151a';
|
|
161
|
+
const ttBg = isDark ? '#1f222a' : '#f0eee7';
|
|
162
|
+
const ttFg = isDark ? '#e8e9ef' : '#15151a';
|
|
163
|
+
|
|
164
|
+
/* Create gradient for tokens area */
|
|
165
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height || 280);
|
|
166
|
+
gradient.addColorStop(0, 'rgba(116, 128, 232, 0.45)');
|
|
167
|
+
gradient.addColorStop(0.6, 'rgba(94, 194, 220, 0.18)');
|
|
168
|
+
gradient.addColorStop(1, 'rgba(94, 194, 168, 0)');
|
|
169
|
+
|
|
170
|
+
const chart = new Chart(ctx, {
|
|
171
|
+
type: 'line',
|
|
122
172
|
data: {
|
|
123
|
-
labels
|
|
173
|
+
labels,
|
|
124
174
|
datasets: [
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
175
|
+
{
|
|
176
|
+
label: 'Tokens (M)',
|
|
177
|
+
data: tokens,
|
|
178
|
+
borderColor: '#7480e8',
|
|
179
|
+
backgroundColor: gradient,
|
|
180
|
+
fill: true,
|
|
181
|
+
tension: 0.4,
|
|
182
|
+
pointRadius: 0,
|
|
183
|
+
pointHoverRadius: 4,
|
|
184
|
+
borderWidth: 1.4,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
label: 'Sessions',
|
|
188
|
+
data: sessions,
|
|
189
|
+
borderColor: sessionColor,
|
|
190
|
+
backgroundColor: 'transparent',
|
|
191
|
+
fill: false,
|
|
192
|
+
tension: 0.4,
|
|
193
|
+
pointRadius: 0,
|
|
194
|
+
pointHoverRadius: 4,
|
|
195
|
+
borderWidth: 1.5,
|
|
196
|
+
},
|
|
128
197
|
],
|
|
129
198
|
},
|
|
130
199
|
options: {
|
|
131
|
-
responsive: true,
|
|
200
|
+
responsive: true,
|
|
201
|
+
maintainAspectRatio: false,
|
|
202
|
+
interaction: { mode: 'index', intersect: false },
|
|
132
203
|
scales: {
|
|
133
|
-
x: {
|
|
134
|
-
|
|
204
|
+
x: {
|
|
205
|
+
grid: { color: gridColor, borderDash: [2, 4], drawBorder: false },
|
|
206
|
+
ticks: { font: { family: 'JetBrains Mono', size: 10 }, color: tickColor, maxTicksLimit: 12 },
|
|
207
|
+
border: { display: false },
|
|
208
|
+
},
|
|
209
|
+
y: {
|
|
210
|
+
grid: { color: gridColor, drawBorder: false },
|
|
211
|
+
ticks: { font: { family: 'JetBrains Mono', size: 10 }, color: tickColor },
|
|
212
|
+
border: { display: false },
|
|
213
|
+
},
|
|
135
214
|
},
|
|
136
215
|
plugins: {
|
|
137
|
-
legend: {
|
|
216
|
+
legend: { display: false },
|
|
138
217
|
tooltip: {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
218
|
+
backgroundColor: ttBg,
|
|
219
|
+
titleColor: ttFg,
|
|
220
|
+
bodyColor: ttFg,
|
|
221
|
+
borderColor: isDark ? 'rgba(232,233,239,0.12)' : 'rgba(21,21,26,0.12)',
|
|
222
|
+
borderWidth: 1,
|
|
223
|
+
cornerRadius: 4,
|
|
224
|
+
padding: 8,
|
|
225
|
+
titleFont: { family: 'JetBrains Mono', size: 11 },
|
|
226
|
+
bodyFont: { family: 'JetBrains Mono', size: 11 },
|
|
145
227
|
},
|
|
146
228
|
},
|
|
147
229
|
},
|
|
148
230
|
});
|
|
149
|
-
setChart(canvasId,
|
|
150
|
-
return instance;
|
|
231
|
+
setChart(canvasId, chart);
|
|
151
232
|
}
|
|
152
233
|
|
|
153
|
-
|
|
154
|
-
export function
|
|
155
|
-
if (!costBreakdown?.models?.length) { destroyChart(canvasId); return null; }
|
|
156
|
-
|
|
157
|
-
const entries = costBreakdown.models.filter(m => m.cost > 0);
|
|
158
|
-
if (entries.length === 0) { destroyChart(canvasId); return null; }
|
|
159
|
-
|
|
160
|
-
const labels = entries.map(m => m.name.length > 22 ? '...' + m.name.slice(-19) : m.name);
|
|
161
|
-
const data = entries.map(m => m.cost);
|
|
162
|
-
const modeColors = { actual: '#22c55e', estimated: '#3b82f6', unknown: '#9ca3af' };
|
|
163
|
-
const colors = entries.map(m => modeColors[m.mode] || modeColors.unknown);
|
|
164
|
-
|
|
234
|
+
/* ── Cache Stack (simple bar) ── */
|
|
235
|
+
export function renderCacheStack(canvasId, cacheRead, cacheCreate, inputTokens) {
|
|
165
236
|
destroyChart(canvasId);
|
|
166
237
|
const canvas = document.getElementById(canvasId);
|
|
167
|
-
if (!canvas) return
|
|
238
|
+
if (!canvas) return;
|
|
168
239
|
const ctx = canvas.getContext('2d');
|
|
169
|
-
const
|
|
240
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
241
|
+
const ttBg = isDark ? '#1f222a' : '#f0eee7';
|
|
242
|
+
const ttFg = isDark ? '#e8e9ef' : '#15151a';
|
|
243
|
+
const forestColor = isDark ? '#5ec2a8' : '#3d7558';
|
|
244
|
+
const rustColor = isDark ? '#7480e8' : '#4a52a8';
|
|
245
|
+
const ochreColor = isDark ? '#c9a86b' : '#9a7836';
|
|
246
|
+
|
|
247
|
+
const chart = new Chart(ctx, {
|
|
170
248
|
type: 'bar',
|
|
171
249
|
data: {
|
|
172
|
-
labels,
|
|
173
|
-
datasets: [
|
|
250
|
+
labels: ['Token 构成'],
|
|
251
|
+
datasets: [
|
|
252
|
+
{ label: '缓存命中', data: [cacheRead], backgroundColor: forestColor, borderRadius: 4 },
|
|
253
|
+
{ label: '新输入', data: [inputTokens], backgroundColor: rustColor, borderRadius: 4 },
|
|
254
|
+
{ label: '缓存写入', data: [cacheCreate], backgroundColor: ochreColor, borderRadius: 4 },
|
|
255
|
+
],
|
|
174
256
|
},
|
|
175
257
|
options: {
|
|
176
|
-
responsive: true,
|
|
258
|
+
responsive: true,
|
|
259
|
+
maintainAspectRatio: false,
|
|
260
|
+
indexAxis: 'y',
|
|
177
261
|
scales: {
|
|
178
|
-
x: {
|
|
179
|
-
|
|
262
|
+
x: {
|
|
263
|
+
stacked: true,
|
|
264
|
+
grid: { display: false },
|
|
265
|
+
ticks: { display: false },
|
|
266
|
+
border: { display: false },
|
|
267
|
+
},
|
|
268
|
+
y: {
|
|
269
|
+
stacked: true,
|
|
270
|
+
grid: { display: false },
|
|
271
|
+
ticks: { display: false },
|
|
272
|
+
border: { display: false },
|
|
273
|
+
},
|
|
180
274
|
},
|
|
181
275
|
plugins: {
|
|
182
276
|
legend: { display: false },
|
|
183
277
|
tooltip: {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
278
|
+
backgroundColor: ttBg,
|
|
279
|
+
titleColor: ttFg,
|
|
280
|
+
bodyColor: ttFg,
|
|
281
|
+
borderColor: isDark ? 'rgba(232,233,239,0.12)' : 'rgba(21,21,26,0.12)',
|
|
282
|
+
borderWidth: 1,
|
|
283
|
+
cornerRadius: 4,
|
|
284
|
+
padding: 8,
|
|
191
285
|
},
|
|
192
286
|
},
|
|
193
287
|
},
|
|
194
288
|
});
|
|
195
|
-
setChart(canvasId,
|
|
196
|
-
|
|
289
|
+
setChart(canvasId, chart);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ── Model Bars (rendered as HTML, not Chart.js, but kept for compatibility) ── */
|
|
293
|
+
export function renderModelBars(containerId, entries) {
|
|
294
|
+
/* This is rendered via Alpine reactive data in app.js */
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ── Drill helpers ── */
|
|
298
|
+
function showDrill(title, html) {
|
|
299
|
+
const modal = document.getElementById('drillModal');
|
|
300
|
+
const t = document.getElementById('drillTitle');
|
|
301
|
+
const b = document.getElementById('drillBody');
|
|
302
|
+
if (t) t.textContent = title;
|
|
303
|
+
if (b) b.innerHTML = html;
|
|
304
|
+
if (modal) modal.style.display = 'flex';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderSessionDrill(project, rows) {
|
|
308
|
+
if (!rows.length) { showDrill(esc(project), '<div class="drill-empty">无数据</div>'); return; }
|
|
309
|
+
const html = '<table class="drill-table">'
|
|
310
|
+
+ '<tr><th></th><th>会话ID</th><th>开始</th><th>时长</th><th>请求</th><th>工具</th><th>文件</th><th>提交</th></tr>'
|
|
311
|
+
+ rows.map((r, i) => {
|
|
312
|
+
const start = r.startTime ? r.startTime.slice(0, 16).replace('T', ' ') : '-';
|
|
313
|
+
const dur = r.duration ? (r.duration >= 3600 ? (r.duration / 3600).toFixed(1) + 'h' : r.duration >= 60 ? Math.round(r.duration / 60) + 'm' : r.duration + 's') : '-';
|
|
314
|
+
const cn = r.commits?.length || 0;
|
|
315
|
+
const toggle = cn > 0 ? `<button class="commit-toggle" data-idx="${i}">▸</button>` : '';
|
|
316
|
+
const tools = [...new Set(r.toolSequence || [])].slice(0, 3).join(', ');
|
|
317
|
+
const fileCount = r.touchedFileCount || 0;
|
|
318
|
+
const commitRows = cn > 0
|
|
319
|
+
? `<tr class="commit-subrow" data-idx="${i}" style="display:none;"><td colspan="8"><table class="commit-subtable">
|
|
320
|
+
<tr><th>hash</th><th>type</th><th>subject</th><th class="num">+行</th><th class="num">-行</th><th>AI</th><th>证据</th></tr>
|
|
321
|
+
${r.commits.map(c => `<tr>
|
|
322
|
+
<td class="hash"><code>${c.hash.slice(0,7)}</code></td>
|
|
323
|
+
<td><span class="commit-type-tag type-${c.type}">${c.type}</span></td>
|
|
324
|
+
<td class="commit-subject" title="${esc(c.subject)}">${esc(c.subject)}</td>
|
|
325
|
+
<td class="num pos">+${fmt(c.linesAdded || 0)}</td>
|
|
326
|
+
<td class="num neg">-${fmt(c.linesDeleted || 0)}</td>
|
|
327
|
+
<td>${c.aiConfidence === 'high' ? 'H' : c.aiConfidence === 'medium' ? 'M' : c.aiConfidence === 'low' ? 'L' : ''}</td>
|
|
328
|
+
<td>${c.aiEvidenceDetails?.matchedFileCount ? `文件交集 ${c.aiEvidenceDetails.matchedFileCount}` : (c.attributionType || '')}</td>
|
|
329
|
+
</tr>`).join('')}
|
|
330
|
+
</table></td></tr>`
|
|
331
|
+
: '';
|
|
332
|
+
return `<tr><td>${toggle}</td><td class="drill-text" title="${esc(r.id)}">${esc(r.id)}</td><td>${start}</td><td>${dur}</td><td>${r.requests || '-'}</td><td class="drill-text">${tools || '-'}</td><td>${fileCount || '-'}</td><td>${cn || '-'}</td></tr>${commitRows}`;
|
|
333
|
+
}).join('')
|
|
334
|
+
+ '</table>';
|
|
335
|
+
showDrill(esc(project) + ' 会话记录', html);
|
|
336
|
+
document.querySelectorAll('.commit-toggle').forEach(btn => {
|
|
337
|
+
btn.addEventListener('click', () => {
|
|
338
|
+
const idx = btn.dataset.idx;
|
|
339
|
+
const sub = document.querySelector(`.commit-subrow[data-idx="${idx}"]`);
|
|
340
|
+
const open = sub.style.display !== 'none';
|
|
341
|
+
sub.style.display = open ? 'none' : '';
|
|
342
|
+
btn.textContent = open ? '▸' : '▾';
|
|
343
|
+
});
|
|
344
|
+
});
|
|
197
345
|
}
|
package/public/config.js
CHANGED
|
@@ -21,30 +21,30 @@ export const COLORS = [
|
|
|
21
21
|
|
|
22
22
|
// 场景色板
|
|
23
23
|
export const SCENARIO_COLORS = {
|
|
24
|
-
'编码': '#
|
|
25
|
-
'测试/QA': '#
|
|
26
|
-
'调试/排错': '#
|
|
27
|
-
'文档': '#
|
|
28
|
-
'阅读/研究': '#
|
|
29
|
-
'规划/设计': '#
|
|
30
|
-
'代码审查': '#
|
|
31
|
-
'其他': '#
|
|
24
|
+
'编码': '#5ec2a8',
|
|
25
|
+
'测试/QA': '#c9b060',
|
|
26
|
+
'调试/排错': '#d47878',
|
|
27
|
+
'文档': '#78a8d4',
|
|
28
|
+
'阅读/研究': '#9878d0',
|
|
29
|
+
'规划/设计': '#d49060',
|
|
30
|
+
'代码审查': '#60b8b8',
|
|
31
|
+
'其他': '#989898',
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// 提交类型色板
|
|
35
35
|
export const COMMIT_TYPE_COLORS = {
|
|
36
|
-
feat: '#
|
|
37
|
-
fix: '#
|
|
38
|
-
refactor: '#
|
|
39
|
-
docs: '#
|
|
40
|
-
test: '#
|
|
41
|
-
chore: '#
|
|
42
|
-
perf: '#
|
|
43
|
-
style: '#
|
|
44
|
-
ci: '#
|
|
45
|
-
build: '#
|
|
46
|
-
revert: '#
|
|
47
|
-
other: '#
|
|
36
|
+
feat: '#5ec2a8',
|
|
37
|
+
fix: '#d47878',
|
|
38
|
+
refactor: '#9878d0',
|
|
39
|
+
docs: '#78a8d4',
|
|
40
|
+
test: '#c9b060',
|
|
41
|
+
chore: '#989898',
|
|
42
|
+
perf: '#d078a8',
|
|
43
|
+
style: '#60b8b8',
|
|
44
|
+
ci: '#d49060',
|
|
45
|
+
build: '#88c860',
|
|
46
|
+
revert: '#d47878',
|
|
47
|
+
other: '#a8a8a8',
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
// 中文字符串
|
|
@@ -136,6 +136,6 @@ export const ID = {
|
|
|
136
136
|
// localStorage keys
|
|
137
137
|
export const STORAGE = {
|
|
138
138
|
CONFIG: 'ccusage-config',
|
|
139
|
-
THEME: '
|
|
139
|
+
THEME: 'lc-theme',
|
|
140
140
|
SIDEBAR_COLLAPSED: 'ccusage-sidebar-collapsed',
|
|
141
141
|
};
|