codex-session-insights 0.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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/bin/codex-insights.js +9 -0
- package/lib/cli.js +1002 -0
- package/lib/codex-data.js +640 -0
- package/lib/llm-insights.js +1486 -0
- package/lib/model-provider.js +589 -0
- package/lib/report.js +1383 -0
- package/lib/types.d.ts +87 -0
- package/package.json +47 -0
package/lib/report.js
ADDED
|
@@ -0,0 +1,1383 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
export function buildReport(threadSummaries, options = {}) {
|
|
5
|
+
const projectCounts = {}
|
|
6
|
+
const modelCounts = {}
|
|
7
|
+
const toolCounts = {}
|
|
8
|
+
const commandKindCounts = {}
|
|
9
|
+
const toolFailureCounts = {}
|
|
10
|
+
const activeHourCounts = {}
|
|
11
|
+
const responseTimes = []
|
|
12
|
+
const userMessageTimestamps = []
|
|
13
|
+
let totalUserMessages = 0
|
|
14
|
+
let totalAssistantMessages = 0
|
|
15
|
+
let totalToolCalls = 0
|
|
16
|
+
let totalFailures = 0
|
|
17
|
+
let totalDurationMinutes = 0
|
|
18
|
+
let totalTokens = 0
|
|
19
|
+
let totalInputTokens = 0
|
|
20
|
+
let totalCachedInputTokens = 0
|
|
21
|
+
let totalOutputTokens = 0
|
|
22
|
+
let totalReasoningOutputTokens = 0
|
|
23
|
+
let totalGitCommits = 0
|
|
24
|
+
let totalGitPushes = 0
|
|
25
|
+
let totalInterruptions = 0
|
|
26
|
+
let totalToolErrors = 0
|
|
27
|
+
let totalLinesAdded = 0
|
|
28
|
+
let totalLinesRemoved = 0
|
|
29
|
+
let totalFilesModified = 0
|
|
30
|
+
let sessionsUsingTaskAgent = 0
|
|
31
|
+
let sessionsUsingMcp = 0
|
|
32
|
+
let sessionsUsingWebSearch = 0
|
|
33
|
+
let sessionsUsingWebFetch = 0
|
|
34
|
+
const toolErrorCategoryCounts = {}
|
|
35
|
+
|
|
36
|
+
for (const thread of threadSummaries) {
|
|
37
|
+
increment(projectCounts, thread.cwd || '(unknown project)')
|
|
38
|
+
increment(modelCounts, thread.model || '(unknown model)')
|
|
39
|
+
|
|
40
|
+
for (const [name, count] of Object.entries(thread.toolCounts)) {
|
|
41
|
+
increment(toolCounts, name, count)
|
|
42
|
+
}
|
|
43
|
+
for (const [name, count] of Object.entries(thread.commandKindCounts)) {
|
|
44
|
+
increment(commandKindCounts, name, count)
|
|
45
|
+
}
|
|
46
|
+
for (const [name, count] of Object.entries(thread.toolFailures)) {
|
|
47
|
+
increment(toolFailureCounts, name, count)
|
|
48
|
+
}
|
|
49
|
+
for (const [name, count] of Object.entries(thread.toolErrorCategories || {})) {
|
|
50
|
+
increment(toolErrorCategoryCounts, name, count)
|
|
51
|
+
}
|
|
52
|
+
for (const hour of thread.activeHours) {
|
|
53
|
+
increment(activeHourCounts, String(hour))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (thread.averageResponseTimeSeconds > 0) {
|
|
57
|
+
responseTimes.push(thread.averageResponseTimeSeconds)
|
|
58
|
+
}
|
|
59
|
+
for (const ts of thread.userMessageTimestamps) {
|
|
60
|
+
userMessageTimestamps.push({ threadId: thread.id, ts: Date.parse(ts) })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
totalUserMessages += thread.userMessages
|
|
64
|
+
totalAssistantMessages += thread.assistantMessages
|
|
65
|
+
totalToolCalls += thread.totalToolCalls
|
|
66
|
+
totalFailures += thread.totalCommandFailures
|
|
67
|
+
totalDurationMinutes += thread.durationMinutes
|
|
68
|
+
totalTokens += thread.tokenUsage.totalTokens
|
|
69
|
+
totalInputTokens += thread.tokenUsage.inputTokens
|
|
70
|
+
totalCachedInputTokens += thread.tokenUsage.cachedInputTokens
|
|
71
|
+
totalOutputTokens += thread.tokenUsage.outputTokens
|
|
72
|
+
totalReasoningOutputTokens += thread.tokenUsage.reasoningOutputTokens
|
|
73
|
+
totalGitCommits += thread.gitCommits
|
|
74
|
+
totalGitPushes += thread.gitPushes
|
|
75
|
+
totalInterruptions += thread.userInterruptions
|
|
76
|
+
totalToolErrors += thread.toolErrors
|
|
77
|
+
totalLinesAdded += thread.linesAdded
|
|
78
|
+
totalLinesRemoved += thread.linesRemoved
|
|
79
|
+
totalFilesModified += thread.filesModified
|
|
80
|
+
if (thread.usesTaskAgent) sessionsUsingTaskAgent += 1
|
|
81
|
+
if (thread.usesMcp) sessionsUsingMcp += 1
|
|
82
|
+
if (thread.usesWebSearch) sessionsUsingWebSearch += 1
|
|
83
|
+
if (thread.usesWebFetch) sessionsUsingWebFetch += 1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sortedThreads = [...threadSummaries].sort(
|
|
87
|
+
(a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt),
|
|
88
|
+
)
|
|
89
|
+
const dateRange = {
|
|
90
|
+
start: sortedThreads.at(-1)?.createdAt.slice(0, 10) ?? '',
|
|
91
|
+
end: sortedThreads[0]?.updatedAt.slice(0, 10) ?? '',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
metadata: {
|
|
96
|
+
generatedAt: new Date().toISOString(),
|
|
97
|
+
codexHome: options.codexHome ?? '',
|
|
98
|
+
language: options.lang ?? 'en',
|
|
99
|
+
days: options.days ?? null,
|
|
100
|
+
threadCount: threadSummaries.length,
|
|
101
|
+
dateRange,
|
|
102
|
+
},
|
|
103
|
+
summary: {
|
|
104
|
+
totalUserMessages,
|
|
105
|
+
totalAssistantMessages,
|
|
106
|
+
totalToolCalls,
|
|
107
|
+
totalFailures,
|
|
108
|
+
totalDurationHours: round(totalDurationMinutes / 60),
|
|
109
|
+
totalTokens,
|
|
110
|
+
totalInputTokens,
|
|
111
|
+
totalCachedInputTokens,
|
|
112
|
+
totalOutputTokens,
|
|
113
|
+
totalReasoningOutputTokens,
|
|
114
|
+
averageResponseTimeSeconds: round(average(responseTimes)),
|
|
115
|
+
overlap: detectOverlaps(userMessageTimestamps),
|
|
116
|
+
totalGitCommits,
|
|
117
|
+
totalGitPushes,
|
|
118
|
+
totalInterruptions,
|
|
119
|
+
totalToolErrors,
|
|
120
|
+
totalLinesAdded,
|
|
121
|
+
totalLinesRemoved,
|
|
122
|
+
totalFilesModified,
|
|
123
|
+
sessionsUsingTaskAgent,
|
|
124
|
+
sessionsUsingMcp,
|
|
125
|
+
sessionsUsingWebSearch,
|
|
126
|
+
sessionsUsingWebFetch,
|
|
127
|
+
},
|
|
128
|
+
charts: {
|
|
129
|
+
projects: topEntries(projectCounts, 10),
|
|
130
|
+
models: topEntries(modelCounts, 10),
|
|
131
|
+
tools: topEntries(toolCounts, 12),
|
|
132
|
+
commandKinds: topEntries(commandKindCounts, 12),
|
|
133
|
+
toolFailures: topEntries(toolFailureCounts, 8),
|
|
134
|
+
toolErrorCategories: topEntries(toolErrorCategoryCounts, 8),
|
|
135
|
+
activeHours: buildHourSeries(activeHourCounts),
|
|
136
|
+
},
|
|
137
|
+
threads: sortedThreads.slice(0, options.threadPreviewLimit ?? 50),
|
|
138
|
+
insights: options.insightsOverride ?? null,
|
|
139
|
+
facets: options.facets || [],
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function writeReportFiles(report, options) {
|
|
144
|
+
if (!report.insights) {
|
|
145
|
+
throw new Error('LLM insights are required before writing a report.')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const outDir = path.resolve(options.outDir)
|
|
149
|
+
await fs.mkdir(outDir, { recursive: true })
|
|
150
|
+
|
|
151
|
+
const jsonPath = options.jsonPath ?? path.join(outDir, 'report.json')
|
|
152
|
+
const htmlPath = options.htmlPath ?? path.join(outDir, 'report.html')
|
|
153
|
+
|
|
154
|
+
await fs.writeFile(jsonPath, JSON.stringify(report, null, 2), 'utf8')
|
|
155
|
+
await fs.writeFile(htmlPath, renderHtmlReport(report), 'utf8')
|
|
156
|
+
|
|
157
|
+
return { jsonPath, htmlPath }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function renderTerminalSummary(report) {
|
|
161
|
+
const text = getReportText(report.metadata.language)
|
|
162
|
+
const lines = []
|
|
163
|
+
lines.push(text.reportTitle)
|
|
164
|
+
lines.push(
|
|
165
|
+
`${report.metadata.threadCount} ${text.threadsShort} | ${report.summary.totalUserMessages} ${text.userMsgsShort} | ${report.summary.totalToolCalls} ${text.toolCallsShort} | ${report.summary.totalDurationHours}h`,
|
|
166
|
+
)
|
|
167
|
+
if (report.analysisUsage?.totalTokens) {
|
|
168
|
+
lines.push(
|
|
169
|
+
`${text.analysisCostLabel}: ${formatMillionTokens(report.analysisUsage.totalTokens)} ${text.across} ${report.analysisUsage.calls} ${text.modelCallsShort}`,
|
|
170
|
+
)
|
|
171
|
+
lines.push(
|
|
172
|
+
` ${text.inputLabel}=${formatMillionTokens(report.analysisUsage.inputTokens)} | ${text.cachedLabel}=${formatMillionTokens(report.analysisUsage.cachedInputTokens)} | ${text.outputLabel}=${formatMillionTokens(report.analysisUsage.outputTokens)}`,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
if (report.metadata.dateRange.start && report.metadata.dateRange.end) {
|
|
176
|
+
lines.push(`${report.metadata.dateRange.start} -> ${report.metadata.dateRange.end}`)
|
|
177
|
+
}
|
|
178
|
+
lines.push('')
|
|
179
|
+
lines.push(`${text.topTools}:`)
|
|
180
|
+
for (const item of report.charts.tools.slice(0, 5)) {
|
|
181
|
+
lines.push(` ${item.label}: ${item.value}`)
|
|
182
|
+
}
|
|
183
|
+
lines.push('')
|
|
184
|
+
lines.push(`${text.topProjects}:`)
|
|
185
|
+
for (const item of report.charts.projects.slice(0, 5)) {
|
|
186
|
+
lines.push(` ${item.label}: ${item.value}`)
|
|
187
|
+
}
|
|
188
|
+
return lines.join('\n')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function renderHtmlReport(report) {
|
|
192
|
+
const text = getReportText(report.metadata.language)
|
|
193
|
+
const insights = report.insights
|
|
194
|
+
if (insights && !insights.__lang) insights.__lang = report.metadata.language
|
|
195
|
+
const analysisUsage = report.analysisUsage || null
|
|
196
|
+
const topTools = renderBarList(report.charts.tools)
|
|
197
|
+
const topProjects = renderBarList(report.charts.projects)
|
|
198
|
+
const commandKinds = renderBarList(report.charts.commandKinds)
|
|
199
|
+
const toolFailures = renderBarList(report.charts.toolFailures)
|
|
200
|
+
const toolErrorCategories = renderBarList(report.charts.toolErrorCategories)
|
|
201
|
+
const activeHours = renderHourHistogram(report.charts.activeHours)
|
|
202
|
+
const analysisByStage = renderBarList(
|
|
203
|
+
(analysisUsage?.byStage || []).map(item => ({ label: item.label, value: item.totalTokens })),
|
|
204
|
+
)
|
|
205
|
+
const analysisByModel = renderBarList(
|
|
206
|
+
(analysisUsage?.byModel || []).map(item => ({ label: item.label, value: item.totalTokens })),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return `<!doctype html>
|
|
210
|
+
<html lang="en">
|
|
211
|
+
<head>
|
|
212
|
+
<meta charset="utf-8">
|
|
213
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
214
|
+
<title>${escapeHtml(text.reportTitle)}</title>
|
|
215
|
+
<style>
|
|
216
|
+
:root {
|
|
217
|
+
--bg: #f7f5ef;
|
|
218
|
+
--paper: #fffdfa;
|
|
219
|
+
--ink: #1d2433;
|
|
220
|
+
--muted: #677084;
|
|
221
|
+
--border: #e6dfd1;
|
|
222
|
+
--accent: #2d5bff;
|
|
223
|
+
--accent-soft: #edf2ff;
|
|
224
|
+
--warm: #fbf0df;
|
|
225
|
+
--green: #dff4e8;
|
|
226
|
+
--shadow: 0 10px 40px rgba(21, 29, 46, 0.08);
|
|
227
|
+
}
|
|
228
|
+
* { box-sizing: border-box; }
|
|
229
|
+
body {
|
|
230
|
+
margin: 0;
|
|
231
|
+
background:
|
|
232
|
+
radial-gradient(circle at top left, rgba(253, 223, 166, 0.45), transparent 24%),
|
|
233
|
+
radial-gradient(circle at top right, rgba(156, 198, 255, 0.35), transparent 30%),
|
|
234
|
+
var(--bg);
|
|
235
|
+
color: var(--ink);
|
|
236
|
+
font-family: ui-serif, Georgia, "Times New Roman", serif;
|
|
237
|
+
}
|
|
238
|
+
main {
|
|
239
|
+
max-width: 1180px;
|
|
240
|
+
margin: 0 auto;
|
|
241
|
+
padding: 32px 18px 72px;
|
|
242
|
+
}
|
|
243
|
+
.hero, .panel, .chart-panel, .thread-card {
|
|
244
|
+
background: rgba(255, 253, 250, 0.92);
|
|
245
|
+
border: 1px solid var(--border);
|
|
246
|
+
border-radius: 24px;
|
|
247
|
+
box-shadow: var(--shadow);
|
|
248
|
+
backdrop-filter: blur(10px);
|
|
249
|
+
}
|
|
250
|
+
.hero {
|
|
251
|
+
padding: 30px;
|
|
252
|
+
margin-bottom: 18px;
|
|
253
|
+
}
|
|
254
|
+
.eyebrow {
|
|
255
|
+
display: inline-block;
|
|
256
|
+
padding: 6px 10px;
|
|
257
|
+
border-radius: 999px;
|
|
258
|
+
background: var(--accent-soft);
|
|
259
|
+
color: var(--accent);
|
|
260
|
+
font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
|
|
261
|
+
letter-spacing: 0.04em;
|
|
262
|
+
text-transform: uppercase;
|
|
263
|
+
}
|
|
264
|
+
h1, h2, h3, h4 {
|
|
265
|
+
margin: 0;
|
|
266
|
+
}
|
|
267
|
+
h1 {
|
|
268
|
+
margin-top: 14px;
|
|
269
|
+
font-size: 42px;
|
|
270
|
+
line-height: 1.05;
|
|
271
|
+
}
|
|
272
|
+
h2 {
|
|
273
|
+
font-size: 28px;
|
|
274
|
+
line-height: 1.1;
|
|
275
|
+
margin-bottom: 12px;
|
|
276
|
+
}
|
|
277
|
+
h3 {
|
|
278
|
+
font-size: 18px;
|
|
279
|
+
line-height: 1.25;
|
|
280
|
+
}
|
|
281
|
+
p, li {
|
|
282
|
+
font: 16px/1.6 ui-sans-serif, system-ui, sans-serif;
|
|
283
|
+
}
|
|
284
|
+
.meta {
|
|
285
|
+
color: var(--muted);
|
|
286
|
+
}
|
|
287
|
+
.summary-grid {
|
|
288
|
+
display: grid;
|
|
289
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
290
|
+
gap: 12px;
|
|
291
|
+
margin-top: 22px;
|
|
292
|
+
}
|
|
293
|
+
.report-nav {
|
|
294
|
+
display: flex;
|
|
295
|
+
flex-wrap: wrap;
|
|
296
|
+
gap: 10px;
|
|
297
|
+
margin-top: 18px;
|
|
298
|
+
}
|
|
299
|
+
.report-nav a {
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
padding: 8px 12px;
|
|
303
|
+
border-radius: 999px;
|
|
304
|
+
background: #fff;
|
|
305
|
+
border: 1px solid var(--border);
|
|
306
|
+
color: var(--ink);
|
|
307
|
+
font: 600 13px/1 ui-sans-serif, system-ui, sans-serif;
|
|
308
|
+
text-decoration: none;
|
|
309
|
+
}
|
|
310
|
+
.stat {
|
|
311
|
+
border-radius: 18px;
|
|
312
|
+
padding: 16px;
|
|
313
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.72), rgba(237,242,255,0.92));
|
|
314
|
+
border: 1px solid rgba(45, 91, 255, 0.08);
|
|
315
|
+
}
|
|
316
|
+
.stat .value {
|
|
317
|
+
font: 700 24px/1 ui-sans-serif, system-ui, sans-serif;
|
|
318
|
+
margin-bottom: 6px;
|
|
319
|
+
}
|
|
320
|
+
.content-grid {
|
|
321
|
+
display: grid;
|
|
322
|
+
grid-template-columns: minmax(0, 1fr) 340px;
|
|
323
|
+
gap: 18px;
|
|
324
|
+
align-items: start;
|
|
325
|
+
}
|
|
326
|
+
.main-column {
|
|
327
|
+
min-width: 0;
|
|
328
|
+
}
|
|
329
|
+
.side-column {
|
|
330
|
+
display: grid;
|
|
331
|
+
gap: 18px;
|
|
332
|
+
}
|
|
333
|
+
.panel, .chart-panel {
|
|
334
|
+
padding: 24px;
|
|
335
|
+
margin-bottom: 18px;
|
|
336
|
+
}
|
|
337
|
+
.at-a-glance {
|
|
338
|
+
display: grid;
|
|
339
|
+
gap: 12px;
|
|
340
|
+
}
|
|
341
|
+
.glance-section {
|
|
342
|
+
padding: 14px 16px;
|
|
343
|
+
border-radius: 18px;
|
|
344
|
+
background: #fff;
|
|
345
|
+
border: 1px solid var(--border);
|
|
346
|
+
font: 15px/1.6 ui-sans-serif, system-ui, sans-serif;
|
|
347
|
+
}
|
|
348
|
+
.see-more {
|
|
349
|
+
display: inline-block;
|
|
350
|
+
margin-top: 8px;
|
|
351
|
+
color: var(--accent);
|
|
352
|
+
font-weight: 600;
|
|
353
|
+
text-decoration: none;
|
|
354
|
+
}
|
|
355
|
+
.narrative p {
|
|
356
|
+
margin: 0 0 12px;
|
|
357
|
+
}
|
|
358
|
+
.key-insight {
|
|
359
|
+
margin-top: 16px;
|
|
360
|
+
padding: 12px 14px;
|
|
361
|
+
border-radius: 16px;
|
|
362
|
+
background: var(--warm);
|
|
363
|
+
font: 15px/1.5 ui-sans-serif, system-ui, sans-serif;
|
|
364
|
+
}
|
|
365
|
+
.project-areas, .insight-grid, .features-grid, .workflow-grid {
|
|
366
|
+
display: grid;
|
|
367
|
+
gap: 12px;
|
|
368
|
+
}
|
|
369
|
+
.project-area, .insight-card, .feature-card, .workflow-card, .agents-card {
|
|
370
|
+
padding: 16px;
|
|
371
|
+
border-radius: 18px;
|
|
372
|
+
border: 1px solid var(--border);
|
|
373
|
+
background: #fff;
|
|
374
|
+
}
|
|
375
|
+
.project-area .topline, .thread-topline {
|
|
376
|
+
display: flex;
|
|
377
|
+
justify-content: space-between;
|
|
378
|
+
gap: 12px;
|
|
379
|
+
align-items: baseline;
|
|
380
|
+
}
|
|
381
|
+
.area-header {
|
|
382
|
+
display: flex;
|
|
383
|
+
justify-content: space-between;
|
|
384
|
+
gap: 12px;
|
|
385
|
+
align-items: baseline;
|
|
386
|
+
margin-bottom: 8px;
|
|
387
|
+
}
|
|
388
|
+
.area-name {
|
|
389
|
+
font: 700 18px/1.3 ui-serif, Georgia, "Times New Roman", serif;
|
|
390
|
+
}
|
|
391
|
+
.area-count {
|
|
392
|
+
color: var(--muted);
|
|
393
|
+
font: 600 13px/1 ui-sans-serif, system-ui, sans-serif;
|
|
394
|
+
}
|
|
395
|
+
.area-desc {
|
|
396
|
+
color: var(--ink);
|
|
397
|
+
}
|
|
398
|
+
.pill {
|
|
399
|
+
display: inline-flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
border-radius: 999px;
|
|
402
|
+
padding: 5px 10px;
|
|
403
|
+
background: var(--accent-soft);
|
|
404
|
+
color: var(--accent);
|
|
405
|
+
font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
|
|
406
|
+
}
|
|
407
|
+
.examples {
|
|
408
|
+
margin: 12px 0 0;
|
|
409
|
+
padding-left: 18px;
|
|
410
|
+
}
|
|
411
|
+
.copy-block {
|
|
412
|
+
margin-top: 12px;
|
|
413
|
+
padding: 12px 14px;
|
|
414
|
+
border-radius: 14px;
|
|
415
|
+
background: #f5f7fb;
|
|
416
|
+
border: 1px solid #dbe2f2;
|
|
417
|
+
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
418
|
+
white-space: pre-wrap;
|
|
419
|
+
word-break: break-word;
|
|
420
|
+
}
|
|
421
|
+
.section-intro {
|
|
422
|
+
margin-top: 0;
|
|
423
|
+
color: var(--muted);
|
|
424
|
+
}
|
|
425
|
+
.subsection {
|
|
426
|
+
margin-top: 18px;
|
|
427
|
+
}
|
|
428
|
+
.subsection h3 {
|
|
429
|
+
margin-bottom: 8px;
|
|
430
|
+
}
|
|
431
|
+
.subsection-intro {
|
|
432
|
+
margin: 0 0 12px;
|
|
433
|
+
color: var(--muted);
|
|
434
|
+
font: 13px/1.6 ui-sans-serif, system-ui, sans-serif;
|
|
435
|
+
}
|
|
436
|
+
.copy-all-row {
|
|
437
|
+
display: flex;
|
|
438
|
+
justify-content: flex-end;
|
|
439
|
+
margin-bottom: 12px;
|
|
440
|
+
}
|
|
441
|
+
.copy-btn, .copy-all-btn {
|
|
442
|
+
border: 1px solid #cfd8eb;
|
|
443
|
+
background: #fff;
|
|
444
|
+
color: var(--ink);
|
|
445
|
+
border-radius: 12px;
|
|
446
|
+
padding: 8px 12px;
|
|
447
|
+
font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
|
|
448
|
+
cursor: pointer;
|
|
449
|
+
}
|
|
450
|
+
.copy-btn.copied, .copy-all-btn.copied {
|
|
451
|
+
background: var(--accent);
|
|
452
|
+
border-color: var(--accent);
|
|
453
|
+
color: #fff;
|
|
454
|
+
}
|
|
455
|
+
.agents-item {
|
|
456
|
+
display: grid;
|
|
457
|
+
gap: 10px;
|
|
458
|
+
padding: 16px;
|
|
459
|
+
border-radius: 18px;
|
|
460
|
+
border: 1px solid var(--border);
|
|
461
|
+
background: #fff;
|
|
462
|
+
}
|
|
463
|
+
.agents-item-head {
|
|
464
|
+
display: grid;
|
|
465
|
+
grid-template-columns: 20px minmax(0, 1fr);
|
|
466
|
+
gap: 12px;
|
|
467
|
+
align-items: start;
|
|
468
|
+
}
|
|
469
|
+
.agents-item-head input {
|
|
470
|
+
margin-top: 3px;
|
|
471
|
+
}
|
|
472
|
+
.agents-item-actions {
|
|
473
|
+
display: flex;
|
|
474
|
+
justify-content: space-between;
|
|
475
|
+
gap: 10px;
|
|
476
|
+
align-items: center;
|
|
477
|
+
}
|
|
478
|
+
.prompt-label, .why-label, .setup-label {
|
|
479
|
+
display: block;
|
|
480
|
+
margin-bottom: 8px;
|
|
481
|
+
color: var(--muted);
|
|
482
|
+
font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
|
|
483
|
+
text-transform: uppercase;
|
|
484
|
+
letter-spacing: 0.04em;
|
|
485
|
+
}
|
|
486
|
+
.copy-row {
|
|
487
|
+
display: grid;
|
|
488
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
489
|
+
gap: 10px;
|
|
490
|
+
align-items: start;
|
|
491
|
+
}
|
|
492
|
+
.copy-row code, .copy-row pre {
|
|
493
|
+
margin: 0;
|
|
494
|
+
}
|
|
495
|
+
.copyable-code {
|
|
496
|
+
padding: 12px 14px;
|
|
497
|
+
border-radius: 14px;
|
|
498
|
+
background: #f5f7fb;
|
|
499
|
+
border: 1px solid #dbe2f2;
|
|
500
|
+
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
501
|
+
white-space: pre-wrap;
|
|
502
|
+
word-break: break-word;
|
|
503
|
+
}
|
|
504
|
+
.feature-why, .pattern-detail, .horizon-tip {
|
|
505
|
+
margin-top: 10px;
|
|
506
|
+
}
|
|
507
|
+
.usage-grid, .usage-breakdown {
|
|
508
|
+
display: grid;
|
|
509
|
+
gap: 12px;
|
|
510
|
+
}
|
|
511
|
+
.usage-grid {
|
|
512
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
513
|
+
margin-top: 16px;
|
|
514
|
+
}
|
|
515
|
+
.usage-detail {
|
|
516
|
+
display: grid;
|
|
517
|
+
gap: 12px;
|
|
518
|
+
}
|
|
519
|
+
.usage-card {
|
|
520
|
+
padding: 16px;
|
|
521
|
+
border-radius: 18px;
|
|
522
|
+
border: 1px solid var(--border);
|
|
523
|
+
background: #fff;
|
|
524
|
+
}
|
|
525
|
+
.usage-card .topline {
|
|
526
|
+
display: flex;
|
|
527
|
+
justify-content: space-between;
|
|
528
|
+
gap: 12px;
|
|
529
|
+
align-items: baseline;
|
|
530
|
+
margin-bottom: 10px;
|
|
531
|
+
}
|
|
532
|
+
.usage-card .meta-row {
|
|
533
|
+
display: flex;
|
|
534
|
+
flex-wrap: wrap;
|
|
535
|
+
gap: 8px 14px;
|
|
536
|
+
color: var(--muted);
|
|
537
|
+
font: 13px/1.5 ui-sans-serif, system-ui, sans-serif;
|
|
538
|
+
}
|
|
539
|
+
.usage-card .token-row {
|
|
540
|
+
display: grid;
|
|
541
|
+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
542
|
+
gap: 10px;
|
|
543
|
+
margin-top: 12px;
|
|
544
|
+
}
|
|
545
|
+
.usage-card .token-box {
|
|
546
|
+
padding: 10px 12px;
|
|
547
|
+
border-radius: 14px;
|
|
548
|
+
background: #faf8f2;
|
|
549
|
+
border: 1px solid var(--border);
|
|
550
|
+
font: 13px/1.4 ui-sans-serif, system-ui, sans-serif;
|
|
551
|
+
}
|
|
552
|
+
.bar-list {
|
|
553
|
+
display: grid;
|
|
554
|
+
gap: 10px;
|
|
555
|
+
}
|
|
556
|
+
.bar-row {
|
|
557
|
+
display: grid;
|
|
558
|
+
gap: 6px;
|
|
559
|
+
}
|
|
560
|
+
.bar-label {
|
|
561
|
+
display: flex;
|
|
562
|
+
justify-content: space-between;
|
|
563
|
+
gap: 12px;
|
|
564
|
+
font: 14px/1.4 ui-sans-serif, system-ui, sans-serif;
|
|
565
|
+
}
|
|
566
|
+
.bar-track {
|
|
567
|
+
height: 10px;
|
|
568
|
+
border-radius: 999px;
|
|
569
|
+
overflow: hidden;
|
|
570
|
+
background: #edf0f6;
|
|
571
|
+
}
|
|
572
|
+
.bar-fill {
|
|
573
|
+
height: 100%;
|
|
574
|
+
background: linear-gradient(90deg, #2d5bff, #5c8bff);
|
|
575
|
+
}
|
|
576
|
+
.hour-grid {
|
|
577
|
+
display: grid;
|
|
578
|
+
gap: 10px;
|
|
579
|
+
}
|
|
580
|
+
.hour-bar {
|
|
581
|
+
display: grid;
|
|
582
|
+
grid-template-columns: 60px minmax(0, 1fr) 40px;
|
|
583
|
+
gap: 10px;
|
|
584
|
+
align-items: center;
|
|
585
|
+
font: 13px/1.3 ui-sans-serif, system-ui, sans-serif;
|
|
586
|
+
}
|
|
587
|
+
.hour-track {
|
|
588
|
+
height: 8px;
|
|
589
|
+
border-radius: 999px;
|
|
590
|
+
background: #ece7ff;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
}
|
|
593
|
+
.hour-fill {
|
|
594
|
+
height: 100%;
|
|
595
|
+
background: linear-gradient(90deg, #7c52ff, #a58bff);
|
|
596
|
+
}
|
|
597
|
+
.thread-list {
|
|
598
|
+
display: grid;
|
|
599
|
+
gap: 14px;
|
|
600
|
+
}
|
|
601
|
+
.thread-card {
|
|
602
|
+
padding: 18px;
|
|
603
|
+
}
|
|
604
|
+
.thread-metrics {
|
|
605
|
+
display: grid;
|
|
606
|
+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
607
|
+
gap: 10px;
|
|
608
|
+
margin-top: 14px;
|
|
609
|
+
}
|
|
610
|
+
.thread-metric {
|
|
611
|
+
padding: 10px 12px;
|
|
612
|
+
border-radius: 14px;
|
|
613
|
+
background: #faf8f2;
|
|
614
|
+
border: 1px solid var(--border);
|
|
615
|
+
font: 13px/1.5 ui-sans-serif, system-ui, sans-serif;
|
|
616
|
+
}
|
|
617
|
+
.fun-ending {
|
|
618
|
+
margin-top: 8px;
|
|
619
|
+
padding-top: 16px;
|
|
620
|
+
border-top: 1px dashed var(--border);
|
|
621
|
+
}
|
|
622
|
+
@media (max-width: 980px) {
|
|
623
|
+
.content-grid {
|
|
624
|
+
grid-template-columns: 1fr;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
@media (max-width: 720px) {
|
|
628
|
+
main {
|
|
629
|
+
padding: 18px 14px 48px;
|
|
630
|
+
}
|
|
631
|
+
h1 {
|
|
632
|
+
font-size: 34px;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
</style>
|
|
636
|
+
</head>
|
|
637
|
+
<body>
|
|
638
|
+
<main>
|
|
639
|
+
<section class="hero">
|
|
640
|
+
<span class="eyebrow">${escapeHtml(text.eyebrow)}</span>
|
|
641
|
+
<h1>${escapeHtml(text.reportTitle)}</h1>
|
|
642
|
+
<p class="meta">${escapeHtml(text.generatedLabel)} ${escapeHtml(report.metadata.generatedAt)} ${escapeHtml(text.generatedFrom)} ${report.metadata.threadCount} ${escapeHtml(text.substantiveThreads)} ${escapeHtml(text.inCodexHome)} ${escapeHtml(report.metadata.codexHome)}.</p>
|
|
643
|
+
<div class="summary-grid">
|
|
644
|
+
${renderStat(text.userMessages, formatNumber(report.summary.totalUserMessages))}
|
|
645
|
+
${renderStat(text.toolCalls, formatNumber(report.summary.totalToolCalls))}
|
|
646
|
+
${renderStat(text.duration, `${report.summary.totalDurationHours}h`)}
|
|
647
|
+
${renderStat(text.tokens, formatNumber(report.summary.totalTokens))}
|
|
648
|
+
${analysisUsage ? renderStat(text.analysisTokens, formatMillionTokens(analysisUsage.totalTokens)) : ''}
|
|
649
|
+
${renderStat(text.commits, formatNumber(report.summary.totalGitCommits))}
|
|
650
|
+
${renderStat(text.filesModified, formatNumber(report.summary.totalFilesModified))}
|
|
651
|
+
${renderStat(text.toolErrors, formatNumber(report.summary.totalToolErrors))}
|
|
652
|
+
${renderStat(text.avgResponse, formatSeconds(report.summary.averageResponseTimeSeconds))}
|
|
653
|
+
</div>
|
|
654
|
+
<nav class="report-nav">
|
|
655
|
+
<a href="#section-work">${escapeHtml(text.whatYouWorkOn)}</a>
|
|
656
|
+
<a href="#section-usage">${escapeHtml(text.howYouUseCodex)}</a>
|
|
657
|
+
<a href="#section-wins">${escapeHtml(text.impressiveThings)}</a>
|
|
658
|
+
<a href="#section-friction">${escapeHtml(text.whereThingsGoWrong)}</a>
|
|
659
|
+
<a href="#section-features">${escapeHtml(text.featuresToTry)}</a>
|
|
660
|
+
<a href="#section-patterns">${escapeHtml(text.newWaysToUseCodex)}</a>
|
|
661
|
+
<a href="#section-horizon">${escapeHtml(text.onTheHorizon)}</a>
|
|
662
|
+
</nav>
|
|
663
|
+
</section>
|
|
664
|
+
<div class="content-grid">
|
|
665
|
+
<div class="main-column">
|
|
666
|
+
${renderAtAGlance(insights)}
|
|
667
|
+
${renderProjectAreas(insights)}
|
|
668
|
+
${renderInteractionStyle(insights)}
|
|
669
|
+
${renderWhatWorks(insights)}
|
|
670
|
+
${renderFriction(insights)}
|
|
671
|
+
${renderSuggestions(insights)}
|
|
672
|
+
${renderOnTheHorizon(insights)}
|
|
673
|
+
</div>
|
|
674
|
+
<aside class="side-column">
|
|
675
|
+
<section class="chart-panel">
|
|
676
|
+
<h2>${escapeHtml(text.topTools)}</h2>
|
|
677
|
+
${topTools}
|
|
678
|
+
</section>
|
|
679
|
+
<section class="chart-panel">
|
|
680
|
+
<h2>${escapeHtml(text.topProjects)}</h2>
|
|
681
|
+
${topProjects}
|
|
682
|
+
</section>
|
|
683
|
+
<section class="chart-panel">
|
|
684
|
+
<h2>${escapeHtml(text.commandKinds)}</h2>
|
|
685
|
+
${commandKinds}
|
|
686
|
+
</section>
|
|
687
|
+
<section class="chart-panel">
|
|
688
|
+
<h2>${escapeHtml(text.failureHotspots)}</h2>
|
|
689
|
+
${toolFailures}
|
|
690
|
+
</section>
|
|
691
|
+
<section class="chart-panel">
|
|
692
|
+
<h2>${escapeHtml(text.errorCategories)}</h2>
|
|
693
|
+
${toolErrorCategories}
|
|
694
|
+
</section>
|
|
695
|
+
<section class="chart-panel">
|
|
696
|
+
<h2>${escapeHtml(text.timeOfDay)}</h2>
|
|
697
|
+
${activeHours}
|
|
698
|
+
</section>
|
|
699
|
+
</aside>
|
|
700
|
+
</div>
|
|
701
|
+
${renderFunEnding(insights)}
|
|
702
|
+
${renderReportMeta(report, { analysisByStage, analysisByModel })}
|
|
703
|
+
</main>
|
|
704
|
+
<script>
|
|
705
|
+
async function copyValue(btn, text) {
|
|
706
|
+
try {
|
|
707
|
+
await navigator.clipboard.writeText(text);
|
|
708
|
+
const original = btn.textContent;
|
|
709
|
+
btn.textContent = ${JSON.stringify(text.copied)};
|
|
710
|
+
btn.classList.add('copied');
|
|
711
|
+
setTimeout(() => {
|
|
712
|
+
btn.textContent = original;
|
|
713
|
+
btn.classList.remove('copied');
|
|
714
|
+
}, 1800);
|
|
715
|
+
} catch {}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
window.copyText = function copyText(btn) {
|
|
719
|
+
const value = btn?.dataset?.copy || '';
|
|
720
|
+
if (!value) return;
|
|
721
|
+
copyValue(btn, value);
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
window.copyAllCheckedAgentInstructions = function copyAllCheckedAgentInstructions(btn) {
|
|
725
|
+
const values = Array.from(document.querySelectorAll('.agents-checkbox:checked'))
|
|
726
|
+
.map(node => node.getAttribute('data-copy') || '')
|
|
727
|
+
.filter(Boolean);
|
|
728
|
+
if (!values.length) return;
|
|
729
|
+
copyValue(btn, values.join('\\n\\n'));
|
|
730
|
+
};
|
|
731
|
+
</script>
|
|
732
|
+
</body>
|
|
733
|
+
</html>`
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function renderAtAGlance(insights) {
|
|
737
|
+
const atAGlance = insights.at_a_glance
|
|
738
|
+
if (!atAGlance) return ''
|
|
739
|
+
const text = getReportText(insights.__lang)
|
|
740
|
+
return `
|
|
741
|
+
<section class="panel">
|
|
742
|
+
<h2>${escapeHtml(text.atAGlance)}</h2>
|
|
743
|
+
<div class="at-a-glance">
|
|
744
|
+
<div class="glance-section"><strong>${escapeHtml(text.whatsWorking)}:</strong> ${escapeHtml(atAGlance.whats_working)} <a class="see-more" href="#section-wins">${escapeHtml(text.impressiveThingsLink)} →</a></div>
|
|
745
|
+
<div class="glance-section"><strong>${escapeHtml(text.whatsHindering)}:</strong> ${escapeHtml(atAGlance.whats_hindering)} <a class="see-more" href="#section-friction">${escapeHtml(text.whereThingsGoWrongLink)} →</a></div>
|
|
746
|
+
<div class="glance-section"><strong>${escapeHtml(text.quickWins)}:</strong> ${escapeHtml(atAGlance.quick_wins)} <a class="see-more" href="#section-features">${escapeHtml(text.featuresToTry)} →</a></div>
|
|
747
|
+
<div class="glance-section"><strong>${escapeHtml(text.ambitiousWorkflows)}:</strong> ${escapeHtml(atAGlance.ambitious_workflows)} <a class="see-more" href="#section-horizon">${escapeHtml(text.onTheHorizon)} →</a></div>
|
|
748
|
+
</div>
|
|
749
|
+
</section>
|
|
750
|
+
`
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function renderProjectAreas(insights) {
|
|
754
|
+
const projectAreas = insights.project_areas?.areas || []
|
|
755
|
+
if (!projectAreas.length) return ''
|
|
756
|
+
const text = getReportText(insights.__lang)
|
|
757
|
+
return `
|
|
758
|
+
<section class="panel">
|
|
759
|
+
<h2 id="section-work">${escapeHtml(text.whatYouWorkOn)}</h2>
|
|
760
|
+
<div class="project-areas">
|
|
761
|
+
${projectAreas
|
|
762
|
+
.map(
|
|
763
|
+
area => `
|
|
764
|
+
<div class="project-area">
|
|
765
|
+
<div class="area-header">
|
|
766
|
+
<span class="area-name">${escapeHtml(area.name)}</span>
|
|
767
|
+
<span class="area-count">~${formatNumber(area.session_count)} ${escapeHtml(text.sessionsLabel)}</span>
|
|
768
|
+
</div>
|
|
769
|
+
<div class="area-desc">${escapeHtml(area.description)}</div>
|
|
770
|
+
</div>
|
|
771
|
+
`,
|
|
772
|
+
)
|
|
773
|
+
.join('')}
|
|
774
|
+
</div>
|
|
775
|
+
</section>
|
|
776
|
+
`
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function renderInteractionStyle(insights) {
|
|
780
|
+
const interactionStyle = insights.interaction_style
|
|
781
|
+
if (!interactionStyle?.narrative) return ''
|
|
782
|
+
const text = getReportText(insights.__lang)
|
|
783
|
+
return `
|
|
784
|
+
<section class="panel">
|
|
785
|
+
<h2 id="section-usage">${escapeHtml(text.howYouUseCodex)}</h2>
|
|
786
|
+
<div class="narrative">
|
|
787
|
+
${markdownToHtml(interactionStyle.narrative)}
|
|
788
|
+
<div class="key-insight"><strong>${escapeHtml(text.keyPattern)}:</strong> ${escapeHtml(interactionStyle.key_pattern || '')}</div>
|
|
789
|
+
</div>
|
|
790
|
+
</section>
|
|
791
|
+
`
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function renderWhatWorks(insights) {
|
|
795
|
+
const whatWorks = insights.what_works
|
|
796
|
+
if (!whatWorks?.impressive_workflows?.length) return ''
|
|
797
|
+
const text = getReportText(insights.__lang)
|
|
798
|
+
return `
|
|
799
|
+
<section class="panel">
|
|
800
|
+
<h2 id="section-wins">${escapeHtml(text.impressiveThings)}</h2>
|
|
801
|
+
<p class="section-intro">${escapeHtml(whatWorks.intro || '')}</p>
|
|
802
|
+
<div class="insight-grid">
|
|
803
|
+
${whatWorks.impressive_workflows
|
|
804
|
+
.map(
|
|
805
|
+
workflow => `
|
|
806
|
+
<div class="insight-card">
|
|
807
|
+
<h3>${escapeHtml(workflow.title)}</h3>
|
|
808
|
+
<p>${escapeHtml(workflow.description)}</p>
|
|
809
|
+
</div>
|
|
810
|
+
`,
|
|
811
|
+
)
|
|
812
|
+
.join('')}
|
|
813
|
+
</div>
|
|
814
|
+
</section>
|
|
815
|
+
`
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function renderFriction(insights) {
|
|
819
|
+
const friction = insights.friction_analysis
|
|
820
|
+
if (!friction?.categories?.length) return ''
|
|
821
|
+
const text = getReportText(insights.__lang)
|
|
822
|
+
return `
|
|
823
|
+
<section class="panel">
|
|
824
|
+
<h2 id="section-friction">${escapeHtml(text.whereThingsGoWrong)}</h2>
|
|
825
|
+
<p class="section-intro">${escapeHtml(friction.intro || '')}</p>
|
|
826
|
+
<div class="insight-grid">
|
|
827
|
+
${friction.categories
|
|
828
|
+
.map(
|
|
829
|
+
category => `
|
|
830
|
+
<div class="insight-card">
|
|
831
|
+
<h3>${escapeHtml(category.category)}</h3>
|
|
832
|
+
<p>${escapeHtml(category.description)}</p>
|
|
833
|
+
<ul class="examples">
|
|
834
|
+
${(category.examples || []).map(example => `<li>${escapeHtml(example)}</li>`).join('')}
|
|
835
|
+
</ul>
|
|
836
|
+
</div>
|
|
837
|
+
`,
|
|
838
|
+
)
|
|
839
|
+
.join('')}
|
|
840
|
+
</div>
|
|
841
|
+
</section>
|
|
842
|
+
`
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function renderSuggestions(insights) {
|
|
846
|
+
const suggestions = insights.suggestions
|
|
847
|
+
if (!suggestions) return ''
|
|
848
|
+
const text = getReportText(insights.__lang)
|
|
849
|
+
const agentItems = suggestions.agents_md_additions || []
|
|
850
|
+
const featureItems = suggestions.features_to_try || []
|
|
851
|
+
const patternItems = suggestions.usage_patterns || []
|
|
852
|
+
|
|
853
|
+
return `
|
|
854
|
+
<section class="panel">
|
|
855
|
+
<h2 id="section-features">${escapeHtml(text.featuresToTry)}</h2>
|
|
856
|
+
${
|
|
857
|
+
agentItems.length
|
|
858
|
+
? `<div class="subsection">
|
|
859
|
+
<h3>${escapeHtml(text.suggestedAgentsAdditions)}</h3>
|
|
860
|
+
<p class="subsection-intro">${escapeHtml(text.agentsSectionIntro)}</p>
|
|
861
|
+
<div class="copy-all-row">
|
|
862
|
+
<button class="copy-all-btn" onclick="copyAllCheckedAgentInstructions(this)">${escapeHtml(text.copyAllChecked)}</button>
|
|
863
|
+
</div>
|
|
864
|
+
<div class="workflow-grid">
|
|
865
|
+
${agentItems
|
|
866
|
+
.map(
|
|
867
|
+
(item, index) => `
|
|
868
|
+
<div class="agents-item">
|
|
869
|
+
<div class="agents-item-head">
|
|
870
|
+
<input type="checkbox" checked class="agents-checkbox" id="agents-item-${index}" data-copy="${escapeAttribute(formatAgentInstruction(item))}">
|
|
871
|
+
<div>
|
|
872
|
+
<label for="agents-item-${index}"><strong>${escapeHtml(text.agentsAddition)}</strong></label>
|
|
873
|
+
<div class="meta">${escapeHtml(item.prompt_scaffold || '')}</div>
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
<div>
|
|
877
|
+
<span class="why-label">${escapeHtml(text.whyThisHelps)}</span>
|
|
878
|
+
<p>${escapeHtml(item.why || '')}</p>
|
|
879
|
+
</div>
|
|
880
|
+
${renderCopyRow(formatAgentInstruction(item), text)}
|
|
881
|
+
</div>
|
|
882
|
+
`,
|
|
883
|
+
)
|
|
884
|
+
.join('')}
|
|
885
|
+
</div>
|
|
886
|
+
</div>`
|
|
887
|
+
: ''
|
|
888
|
+
}
|
|
889
|
+
${
|
|
890
|
+
featureItems.length
|
|
891
|
+
? `<div class="subsection">
|
|
892
|
+
<h3>${escapeHtml(text.existingFeaturesHeading)}</h3>
|
|
893
|
+
<p class="subsection-intro">${escapeHtml(text.featuresSectionIntro)}</p>
|
|
894
|
+
<div class="features-grid">
|
|
895
|
+
${featureItems
|
|
896
|
+
.map(
|
|
897
|
+
feature => `
|
|
898
|
+
<div class="feature-card">
|
|
899
|
+
<div class="topline">
|
|
900
|
+
<h3>${escapeHtml(feature.feature)}</h3>
|
|
901
|
+
<span class="pill">${escapeHtml(feature.one_liner)}</span>
|
|
902
|
+
</div>
|
|
903
|
+
<div class="feature-why"><strong>${escapeHtml(text.whyForYou)}:</strong> ${escapeHtml(feature.why_for_you)}</div>
|
|
904
|
+
${feature.example_code ? `<div class="setup-label">${escapeHtml(text.tryThis)}</div>${renderCopyRow(feature.example_code, text)}` : ''}
|
|
905
|
+
</div>
|
|
906
|
+
`,
|
|
907
|
+
)
|
|
908
|
+
.join('')}
|
|
909
|
+
</div>
|
|
910
|
+
</div>`
|
|
911
|
+
: ''
|
|
912
|
+
}
|
|
913
|
+
${
|
|
914
|
+
patternItems.length
|
|
915
|
+
? `<div class="subsection">
|
|
916
|
+
<h3 id="section-patterns">${escapeHtml(text.newWaysToUseCodex)}</h3>
|
|
917
|
+
<p class="subsection-intro">${escapeHtml(text.patternsSectionIntro)}</p>
|
|
918
|
+
<div class="workflow-grid">
|
|
919
|
+
${patternItems
|
|
920
|
+
.map(
|
|
921
|
+
pattern => `
|
|
922
|
+
<div class="workflow-card">
|
|
923
|
+
<h3>${escapeHtml(pattern.title)}</h3>
|
|
924
|
+
<p><strong>${escapeHtml(pattern.suggestion)}</strong></p>
|
|
925
|
+
<p class="pattern-detail">${escapeHtml(pattern.detail)}</p>
|
|
926
|
+
${pattern.copyable_prompt ? `<div class="prompt-label">${escapeHtml(text.pasteIntoCodex)}</div>${renderCopyRow(pattern.copyable_prompt, text)}` : ''}
|
|
927
|
+
</div>
|
|
928
|
+
`,
|
|
929
|
+
)
|
|
930
|
+
.join('')}
|
|
931
|
+
</div>
|
|
932
|
+
</div>`
|
|
933
|
+
: ''
|
|
934
|
+
}
|
|
935
|
+
</section>
|
|
936
|
+
`
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function renderOnTheHorizon(insights) {
|
|
940
|
+
const horizon = insights.on_the_horizon
|
|
941
|
+
if (!horizon?.opportunities?.length) return ''
|
|
942
|
+
const text = getReportText(insights.__lang)
|
|
943
|
+
return `
|
|
944
|
+
<section class="panel">
|
|
945
|
+
<h2 id="section-horizon">${escapeHtml(text.onTheHorizon)}</h2>
|
|
946
|
+
<p class="section-intro">${escapeHtml(horizon.intro || '')}</p>
|
|
947
|
+
<div class="insight-grid">
|
|
948
|
+
${horizon.opportunities
|
|
949
|
+
.map(
|
|
950
|
+
item => `
|
|
951
|
+
<div class="insight-card">
|
|
952
|
+
<h3>${escapeHtml(item.title)}</h3>
|
|
953
|
+
<p>${escapeHtml(item.whats_possible)}</p>
|
|
954
|
+
<div class="horizon-tip"><strong>${escapeHtml(text.gettingStarted)}:</strong> ${escapeHtml(item.how_to_try || '')}</div>
|
|
955
|
+
${item.copyable_prompt ? `<div class="prompt-label">${escapeHtml(text.pasteIntoCodex)}</div>${renderCopyRow(item.copyable_prompt, text)}` : ''}
|
|
956
|
+
</div>
|
|
957
|
+
`,
|
|
958
|
+
)
|
|
959
|
+
.join('')}
|
|
960
|
+
</div>
|
|
961
|
+
</section>
|
|
962
|
+
`
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function renderReportMeta(report, context = {}) {
|
|
966
|
+
const text = getReportText(report.metadata.language)
|
|
967
|
+
const usage = report.analysisUsage
|
|
968
|
+
const hasUsage = Boolean(usage?.totalTokens)
|
|
969
|
+
const analysisByStage = context.analysisByStage || ''
|
|
970
|
+
const analysisByModel = context.analysisByModel || ''
|
|
971
|
+
|
|
972
|
+
const freshInputTokens = Math.max(0, usage.inputTokens - usage.cachedInputTokens)
|
|
973
|
+
|
|
974
|
+
return `
|
|
975
|
+
<section class="panel">
|
|
976
|
+
<h2>${escapeHtml(text.aboutThisReport)}</h2>
|
|
977
|
+
<p class="section-intro">${escapeHtml(text.aboutThisReportIntro)}</p>
|
|
978
|
+
<div class="usage-grid">
|
|
979
|
+
${renderStat(text.threadsAnalyzed, formatNumber(report.metadata.threadCount))}
|
|
980
|
+
${renderStat(text.dateRange, `${escapeHtml(report.metadata.dateRange.start)} -> ${escapeHtml(report.metadata.dateRange.end)}`)}
|
|
981
|
+
${hasUsage ? renderStat(text.analysisTokens, formatMillionTokens(usage.totalTokens)) : ''}
|
|
982
|
+
${hasUsage ? renderStat(text.modelCalls, formatNumber(usage.calls)) : ''}
|
|
983
|
+
${hasUsage ? renderStat(text.cachedInput, formatMillionTokens(usage.cachedInputTokens)) : ''}
|
|
984
|
+
${renderStat(text.historicalSessionTokens, formatNumber(report.summary.totalTokens))}
|
|
985
|
+
</div>
|
|
986
|
+
${
|
|
987
|
+
hasUsage
|
|
988
|
+
? `<div class="usage-detail" style="margin-top:18px;">
|
|
989
|
+
<div class="usage-breakdown">
|
|
990
|
+
<h3>${escapeHtml(text.analysisCostByStage)}</h3>
|
|
991
|
+
${analysisByStage}
|
|
992
|
+
</div>
|
|
993
|
+
<div class="usage-breakdown">
|
|
994
|
+
<h3>${escapeHtml(text.analysisCostByModel)}</h3>
|
|
995
|
+
${analysisByModel}
|
|
996
|
+
</div>
|
|
997
|
+
<div class="usage-breakdown">
|
|
998
|
+
<h3>${escapeHtml(text.usageDetails)}</h3>
|
|
999
|
+
${renderUsageCard({
|
|
1000
|
+
label: usage.provider || report.provider || 'unknown',
|
|
1001
|
+
calls: usage.calls,
|
|
1002
|
+
inputTokens: usage.inputTokens,
|
|
1003
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
1004
|
+
outputTokens: usage.outputTokens,
|
|
1005
|
+
totalTokens: usage.totalTokens,
|
|
1006
|
+
})}
|
|
1007
|
+
<p class="meta">${escapeHtml(text.freshInput)}: ${formatMillionTokens(freshInputTokens)}. ${escapeHtml(text.analysisCostFootnote)}</p>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>`
|
|
1010
|
+
: ''
|
|
1011
|
+
}
|
|
1012
|
+
</section>
|
|
1013
|
+
`
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function renderFunEnding(insights) {
|
|
1017
|
+
const ending = insights.fun_ending
|
|
1018
|
+
if (!ending?.headline) return ''
|
|
1019
|
+
const text = getReportText(insights.__lang)
|
|
1020
|
+
return `
|
|
1021
|
+
<section class="panel fun-ending">
|
|
1022
|
+
<h2>${escapeHtml(text.oneMoreThing)}</h2>
|
|
1023
|
+
<h3>${escapeHtml(ending.headline)}</h3>
|
|
1024
|
+
<p>${escapeHtml(ending.detail || '')}</p>
|
|
1025
|
+
</section>
|
|
1026
|
+
`
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function renderUsageCard(item) {
|
|
1030
|
+
const freshInputTokens = Math.max(0, Number(item.inputTokens || 0) - Number(item.cachedInputTokens || 0))
|
|
1031
|
+
return `
|
|
1032
|
+
<div class="usage-card">
|
|
1033
|
+
<div class="topline">
|
|
1034
|
+
<h3>${escapeHtml(item.label)}</h3>
|
|
1035
|
+
<span class="pill">${formatNumber(item.calls)} calls</span>
|
|
1036
|
+
</div>
|
|
1037
|
+
<div class="meta-row">
|
|
1038
|
+
<span>Total: ${formatMillionTokens(item.totalTokens)}</span>
|
|
1039
|
+
<span>Fresh input share: ${formatPercent(freshInputTokens, item.totalTokens)}</span>
|
|
1040
|
+
<span>Output share: ${formatPercent(item.outputTokens, item.totalTokens)}</span>
|
|
1041
|
+
</div>
|
|
1042
|
+
<div class="token-row">
|
|
1043
|
+
<div class="token-box"><strong>Total</strong><br>${formatMillionTokens(item.totalTokens)}</div>
|
|
1044
|
+
<div class="token-box"><strong>Input</strong><br>${formatMillionTokens(item.inputTokens)}</div>
|
|
1045
|
+
<div class="token-box"><strong>Cached Input</strong><br>${formatMillionTokens(item.cachedInputTokens)}</div>
|
|
1046
|
+
<div class="token-box"><strong>Fresh Input</strong><br>${formatMillionTokens(freshInputTokens)}</div>
|
|
1047
|
+
<div class="token-box"><strong>Output</strong><br>${formatMillionTokens(item.outputTokens)}</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
`
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function renderStat(label, value) {
|
|
1054
|
+
return `<div class="stat"><div class="value">${escapeHtml(String(value))}</div><div>${escapeHtml(label)}</div></div>`
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function renderBarList(items) {
|
|
1058
|
+
if (!items.length) return '<p class="meta">No data available.</p>'
|
|
1059
|
+
const maxValue = Math.max(...items.map(item => item.value), 1)
|
|
1060
|
+
return `<div class="bar-list">${items
|
|
1061
|
+
.map(
|
|
1062
|
+
item => `
|
|
1063
|
+
<div class="bar-row">
|
|
1064
|
+
<div class="bar-label">
|
|
1065
|
+
<span>${escapeHtml(item.label)}</span>
|
|
1066
|
+
<strong>${formatNumber(item.value)}</strong>
|
|
1067
|
+
</div>
|
|
1068
|
+
<div class="bar-track"><div class="bar-fill" style="width:${Math.max(6, (item.value / maxValue) * 100)}%"></div></div>
|
|
1069
|
+
</div>
|
|
1070
|
+
`,
|
|
1071
|
+
)
|
|
1072
|
+
.join('')}</div>`
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function renderHourHistogram(hourSeries) {
|
|
1076
|
+
if (!hourSeries.length) return '<p class="meta">No data available.</p>'
|
|
1077
|
+
const grouped = [
|
|
1078
|
+
{ label: 'Night', hours: [0, 1, 2, 3, 4, 5] },
|
|
1079
|
+
{ label: 'Morning', hours: [6, 7, 8, 9, 10, 11] },
|
|
1080
|
+
{ label: 'Afternoon', hours: [12, 13, 14, 15, 16, 17] },
|
|
1081
|
+
{ label: 'Evening', hours: [18, 19, 20, 21, 22, 23] },
|
|
1082
|
+
].map(group => ({
|
|
1083
|
+
label: group.label,
|
|
1084
|
+
value: group.hours.reduce((sum, hour) => sum + Number(hourSeries[hour]?.value || 0), 0),
|
|
1085
|
+
}))
|
|
1086
|
+
|
|
1087
|
+
const maxValue = Math.max(...grouped.map(item => item.value), 1)
|
|
1088
|
+
return `<div class="hour-grid">${grouped
|
|
1089
|
+
.map(
|
|
1090
|
+
item => `
|
|
1091
|
+
<div class="hour-bar">
|
|
1092
|
+
<span>${escapeHtml(item.label)}</span>
|
|
1093
|
+
<div class="hour-track"><div class="hour-fill" style="width:${(item.value / maxValue) * 100}%"></div></div>
|
|
1094
|
+
<strong>${formatNumber(item.value)}</strong>
|
|
1095
|
+
</div>
|
|
1096
|
+
`,
|
|
1097
|
+
)
|
|
1098
|
+
.join('')}</div>`
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function markdownToHtml(text) {
|
|
1102
|
+
return String(text || '')
|
|
1103
|
+
.split('\n\n')
|
|
1104
|
+
.filter(Boolean)
|
|
1105
|
+
.map(paragraph => `<p>${escapeHtml(paragraph).replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>')}</p>`)
|
|
1106
|
+
.join('\n')
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function renderCopyRow(value, text) {
|
|
1110
|
+
const content = String(value || '').trim()
|
|
1111
|
+
if (!content) return ''
|
|
1112
|
+
return `
|
|
1113
|
+
<div class="copy-row">
|
|
1114
|
+
<pre class="copyable-code"><code>${escapeHtml(content)}</code></pre>
|
|
1115
|
+
<button class="copy-btn" data-copy="${escapeAttribute(content)}" onclick="copyText(this)">${escapeHtml(text.copy)}</button>
|
|
1116
|
+
</div>
|
|
1117
|
+
`
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function formatAgentInstruction(item) {
|
|
1121
|
+
const placement = String(item?.prompt_scaffold || '').trim()
|
|
1122
|
+
const addition = String(item?.addition || '').trim()
|
|
1123
|
+
if (placement && addition) {
|
|
1124
|
+
return `${placement}\n\n${addition}`
|
|
1125
|
+
}
|
|
1126
|
+
return addition || placement
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function topEntries(map, limit) {
|
|
1130
|
+
return Object.entries(map)
|
|
1131
|
+
.sort((a, b) => b[1] - a[1])
|
|
1132
|
+
.slice(0, limit)
|
|
1133
|
+
.map(([label, value]) => ({ label, value }))
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function buildHourSeries(hourMap) {
|
|
1137
|
+
return Array.from({ length: 24 }, (_, hour) => ({
|
|
1138
|
+
hour,
|
|
1139
|
+
value: Number(hourMap[String(hour)] ?? 0),
|
|
1140
|
+
}))
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function increment(map, key, amount = 1) {
|
|
1144
|
+
map[key] = (map[key] || 0) + amount
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function round(value) {
|
|
1148
|
+
return Math.round(value * 10) / 10
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function formatInteger(value) {
|
|
1152
|
+
return new Intl.NumberFormat('en-US').format(Math.round(Number(value || 0)))
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function formatCompactTokens(value) {
|
|
1156
|
+
const number = Number(value || 0)
|
|
1157
|
+
if (number >= 1_000_000) return `${round(number / 1_000_000)}M`
|
|
1158
|
+
if (number >= 1_000) return `${round(number / 1_000)}k`
|
|
1159
|
+
return String(Math.round(number))
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function formatMillionTokens(value) {
|
|
1163
|
+
const number = Number(value || 0)
|
|
1164
|
+
if (number >= 1_000_000) return `${round(number / 1_000_000)}M tokens`
|
|
1165
|
+
return `${round(number / 1_000)}K tokens`
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function formatPercent(value, total) {
|
|
1169
|
+
const numerator = Number(value || 0)
|
|
1170
|
+
const denominator = Number(total || 0)
|
|
1171
|
+
if (!denominator) return '0%'
|
|
1172
|
+
return `${round((numerator / denominator) * 100)}%`
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function average(values) {
|
|
1176
|
+
if (!values.length) return 0
|
|
1177
|
+
return round(values.reduce((sum, value) => sum + value, 0) / values.length)
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function formatNumber(value) {
|
|
1181
|
+
return new Intl.NumberFormat('en-US').format(Math.round(value))
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function formatSeconds(value) {
|
|
1185
|
+
if (!value) return 'n/a'
|
|
1186
|
+
return `${round(value)}s`
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function detectOverlaps(userMessageTimestamps) {
|
|
1190
|
+
const sorted = [...userMessageTimestamps].sort((a, b) => a.ts - b.ts)
|
|
1191
|
+
const windowMs = 30 * 60 * 1000
|
|
1192
|
+
const pairs = new Set()
|
|
1193
|
+
|
|
1194
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
1195
|
+
const current = sorted[i]
|
|
1196
|
+
for (let j = i + 1; j < sorted.length; j += 1) {
|
|
1197
|
+
const next = sorted[j]
|
|
1198
|
+
if (next.ts - current.ts > windowMs) break
|
|
1199
|
+
if (next.threadId === current.threadId) continue
|
|
1200
|
+
pairs.add([current.threadId, next.threadId].sort().join(':'))
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const threadsInvolved = new Set()
|
|
1205
|
+
for (const pair of pairs) {
|
|
1206
|
+
const [left, right] = pair.split(':')
|
|
1207
|
+
if (left) threadsInvolved.add(left)
|
|
1208
|
+
if (right) threadsInvolved.add(right)
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return {
|
|
1212
|
+
overlapEvents: pairs.size,
|
|
1213
|
+
threadsInvolved: threadsInvolved.size,
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function escapeHtml(value) {
|
|
1218
|
+
return String(value)
|
|
1219
|
+
.replaceAll('&', '&')
|
|
1220
|
+
.replaceAll('<', '<')
|
|
1221
|
+
.replaceAll('>', '>')
|
|
1222
|
+
.replaceAll('"', '"')
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function escapeAttribute(value) {
|
|
1226
|
+
return escapeHtml(String(value)).replaceAll("'", ''').replaceAll('\n', ' ')
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function getReportText(lang) {
|
|
1230
|
+
if (lang === 'zh-CN') {
|
|
1231
|
+
return {
|
|
1232
|
+
reportTitle: 'Codex Insights',
|
|
1233
|
+
eyebrow: 'Codex 会话报告',
|
|
1234
|
+
generatedLabel: '生成时间',
|
|
1235
|
+
generatedFrom: ',基于',
|
|
1236
|
+
substantiveThreads: '个有效线程',
|
|
1237
|
+
inCodexHome: ',Codex 目录',
|
|
1238
|
+
userMessages: '用户消息',
|
|
1239
|
+
toolCalls: '工具调用',
|
|
1240
|
+
duration: '时长',
|
|
1241
|
+
tokens: '历史 Tokens',
|
|
1242
|
+
analysisTokens: '分析 Tokens',
|
|
1243
|
+
commits: '提交数',
|
|
1244
|
+
filesModified: '修改文件',
|
|
1245
|
+
toolErrors: '工具错误',
|
|
1246
|
+
avgResponse: '平均响应',
|
|
1247
|
+
topTools: 'Top Tools',
|
|
1248
|
+
topProjects: 'Top Projects',
|
|
1249
|
+
commandKinds: '命令类型',
|
|
1250
|
+
failureHotspots: '失败热点',
|
|
1251
|
+
errorCategories: '错误分类',
|
|
1252
|
+
timeOfDay: '活跃时段',
|
|
1253
|
+
atAGlance: '一眼看懂',
|
|
1254
|
+
whatsWorking: '目前顺的地方',
|
|
1255
|
+
whatsHindering: '阻碍你的地方',
|
|
1256
|
+
quickWins: '可以立刻尝试的优化',
|
|
1257
|
+
ambitiousWorkflows: '值得尝试的更强工作流',
|
|
1258
|
+
sessionsLabel: '次会话',
|
|
1259
|
+
impressiveThingsLink: '做得好的地方',
|
|
1260
|
+
whereThingsGoWrongLink: '容易出问题的地方',
|
|
1261
|
+
whatYouWorkOn: '你主要在做什么',
|
|
1262
|
+
howYouUseCodex: '你是怎么用 Codex 的',
|
|
1263
|
+
keyPattern: '关键模式',
|
|
1264
|
+
impressiveThings: '做得好的地方',
|
|
1265
|
+
whereThingsGoWrong: '容易出问题的地方',
|
|
1266
|
+
featuresToTry: '可以尝试的能力',
|
|
1267
|
+
existingFeaturesHeading: '现有 Codex 能力,建议你先试这些',
|
|
1268
|
+
newWaysToUseCodex: '新的 Codex 使用方式',
|
|
1269
|
+
suggestedAgentsAdditions: '建议补进 AGENTS.md 的内容',
|
|
1270
|
+
agentsSectionIntro: '勾选后可一起复制,再放进你的 AGENTS.md 或仓库规范里。',
|
|
1271
|
+
featuresSectionIntro: '下面这些是现成能力。不是抽象建议,而是你现在就可以直接试的配置、命令或片段。',
|
|
1272
|
+
patternsSectionIntro: '这些不是新功能,而是更适合你当前工作方式的用法。直接复制下面的提示词到 Codex 里试。',
|
|
1273
|
+
agentsAddition: 'AGENTS.md 建议补充',
|
|
1274
|
+
copyAllChecked: '复制所有勾选项',
|
|
1275
|
+
copy: '复制',
|
|
1276
|
+
copied: '已复制',
|
|
1277
|
+
whyThisHelps: '为什么这条值得加',
|
|
1278
|
+
whyForYou: '为什么适合你',
|
|
1279
|
+
tryThis: '可以直接试这个',
|
|
1280
|
+
pasteIntoCodex: '粘贴到 Codex:',
|
|
1281
|
+
gettingStarted: '开始方式',
|
|
1282
|
+
onTheHorizon: '下一步可以做什么',
|
|
1283
|
+
aboutThisReport: '关于这份报告',
|
|
1284
|
+
aboutThisReportIntro: '这里是这份报告的生成附录。上面的主体部分仍然聚焦在你的会话模式和协作习惯。',
|
|
1285
|
+
threadsAnalyzed: '分析线程数',
|
|
1286
|
+
dateRange: '时间范围',
|
|
1287
|
+
modelCalls: '模型调用',
|
|
1288
|
+
cachedInput: '缓存输入',
|
|
1289
|
+
historicalSessionTokens: '历史会话 Tokens',
|
|
1290
|
+
analysisCostByStage: '按阶段拆分的分析成本',
|
|
1291
|
+
analysisCostByModel: '按模型拆分的分析成本',
|
|
1292
|
+
usageDetails: '使用详情',
|
|
1293
|
+
freshInput: '新输入',
|
|
1294
|
+
analysisCostFootnote: '这是生成报告本身的额外消耗,不包含上面统计的历史 Codex 会话 tokens。',
|
|
1295
|
+
oneMoreThing: '最后一件事',
|
|
1296
|
+
threadsShort: 'threads',
|
|
1297
|
+
userMsgsShort: '用户消息',
|
|
1298
|
+
toolCallsShort: '工具调用',
|
|
1299
|
+
analysisCostLabel: '分析成本',
|
|
1300
|
+
across: '共',
|
|
1301
|
+
modelCallsShort: '次模型调用',
|
|
1302
|
+
inputLabel: '输入',
|
|
1303
|
+
cachedLabel: '缓存',
|
|
1304
|
+
outputLabel: '输出',
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return {
|
|
1309
|
+
reportTitle: 'Codex Insights',
|
|
1310
|
+
eyebrow: 'Codex Usage Report',
|
|
1311
|
+
generatedLabel: 'Generated',
|
|
1312
|
+
generatedFrom: 'from',
|
|
1313
|
+
substantiveThreads: 'substantive threads',
|
|
1314
|
+
inCodexHome: 'in',
|
|
1315
|
+
userMessages: 'User Messages',
|
|
1316
|
+
toolCalls: 'Tool Calls',
|
|
1317
|
+
duration: 'Duration',
|
|
1318
|
+
tokens: 'Historical Tokens',
|
|
1319
|
+
analysisTokens: 'Analysis Tokens',
|
|
1320
|
+
commits: 'Commits',
|
|
1321
|
+
filesModified: 'Files Modified',
|
|
1322
|
+
toolErrors: 'Tool Errors',
|
|
1323
|
+
avgResponse: 'Avg Response',
|
|
1324
|
+
topTools: 'Top Tools',
|
|
1325
|
+
topProjects: 'Top Projects',
|
|
1326
|
+
commandKinds: 'Command Kinds',
|
|
1327
|
+
failureHotspots: 'Failure Hotspots',
|
|
1328
|
+
errorCategories: 'Error Categories',
|
|
1329
|
+
timeOfDay: 'Time of Day',
|
|
1330
|
+
atAGlance: 'At a Glance',
|
|
1331
|
+
whatsWorking: "What's working",
|
|
1332
|
+
whatsHindering: "What's hindering you",
|
|
1333
|
+
quickWins: 'Quick wins to try',
|
|
1334
|
+
ambitiousWorkflows: 'Ambitious workflows',
|
|
1335
|
+
sessionsLabel: 'sessions',
|
|
1336
|
+
impressiveThingsLink: 'Impressive Things You Did',
|
|
1337
|
+
whereThingsGoWrongLink: 'Where Things Go Wrong',
|
|
1338
|
+
whatYouWorkOn: 'What You Work On',
|
|
1339
|
+
howYouUseCodex: 'How You Use Codex',
|
|
1340
|
+
keyPattern: 'Key pattern',
|
|
1341
|
+
impressiveThings: 'Impressive Things You Did',
|
|
1342
|
+
whereThingsGoWrong: 'Where Things Go Wrong',
|
|
1343
|
+
featuresToTry: 'Features to Try',
|
|
1344
|
+
existingFeaturesHeading: 'Existing Codex Features to Try',
|
|
1345
|
+
newWaysToUseCodex: 'New Ways to Use Codex',
|
|
1346
|
+
suggestedAgentsAdditions: 'Suggested AGENTS.md Additions',
|
|
1347
|
+
agentsSectionIntro: 'Check the items you want, then copy them into your AGENTS.md or repo playbook.',
|
|
1348
|
+
featuresSectionIntro: 'These are existing Codex features worth trying now, not abstract advice.',
|
|
1349
|
+
patternsSectionIntro: 'These are concrete ways to use Codex differently based on how you already work.',
|
|
1350
|
+
agentsAddition: 'AGENTS.md Addition',
|
|
1351
|
+
copyAllChecked: 'Copy All Checked',
|
|
1352
|
+
copy: 'Copy',
|
|
1353
|
+
copied: 'Copied',
|
|
1354
|
+
whyThisHelps: 'Why this helps',
|
|
1355
|
+
whyForYou: 'Why for you',
|
|
1356
|
+
tryThis: 'Try this',
|
|
1357
|
+
pasteIntoCodex: 'Paste into Codex:',
|
|
1358
|
+
gettingStarted: 'Getting started',
|
|
1359
|
+
onTheHorizon: 'On the Horizon',
|
|
1360
|
+
aboutThisReport: 'About This Report',
|
|
1361
|
+
aboutThisReportIntro: 'A compact appendix for how this page was generated. The main report above stays focused on your session patterns.',
|
|
1362
|
+
threadsAnalyzed: 'Threads Analyzed',
|
|
1363
|
+
dateRange: 'Date Range',
|
|
1364
|
+
modelCalls: 'Model Calls',
|
|
1365
|
+
cachedInput: 'Cached Input',
|
|
1366
|
+
historicalSessionTokens: 'Historical Session Tokens',
|
|
1367
|
+
analysisCostByStage: 'Analysis Cost by Stage',
|
|
1368
|
+
analysisCostByModel: 'Analysis Cost by Model',
|
|
1369
|
+
usageDetails: 'Usage Details',
|
|
1370
|
+
freshInput: 'Fresh input',
|
|
1371
|
+
analysisCostFootnote: 'This is the extra spend for generating the report, separate from the historical Codex session tokens above.',
|
|
1372
|
+
oneMoreThing: 'One More Thing',
|
|
1373
|
+
threadsShort: 'threads',
|
|
1374
|
+
userMsgsShort: 'user msgs',
|
|
1375
|
+
toolCallsShort: 'tool calls',
|
|
1376
|
+
analysisCostLabel: 'Analysis cost',
|
|
1377
|
+
across: 'across',
|
|
1378
|
+
modelCallsShort: 'model calls',
|
|
1379
|
+
inputLabel: 'input',
|
|
1380
|
+
cachedLabel: 'cached',
|
|
1381
|
+
outputLabel: 'output',
|
|
1382
|
+
}
|
|
1383
|
+
}
|