codexmate 0.0.20 → 0.0.22

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.
Files changed (96) hide show
  1. package/README.md +289 -152
  2. package/README.zh.md +321 -0
  3. package/cli/agents-files.js +224 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +1044 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/openai-bridge.js +950 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/session-usage.concurrent.js +28 -0
  12. package/cli/session-usage.js +112 -0
  13. package/cli/session-usage.models.js +176 -0
  14. package/cli/skills.js +1141 -0
  15. package/cli/zip-commands.js +510 -0
  16. package/cli.js +9408 -9719
  17. package/lib/cli-models-utils.js +109 -1
  18. package/lib/cli-path-utils.js +69 -0
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/download-artifacts.js +77 -0
  21. package/lib/task-orchestrator.js +869 -0
  22. package/package.json +14 -10
  23. package/res/logo.png +0 -0
  24. package/res/vue.global.prod.js +13 -0
  25. package/web-ui/app.js +193 -15
  26. package/web-ui/index.html +5 -1
  27. package/web-ui/logic.agents-diff.mjs +1 -1
  28. package/web-ui/logic.claude.mjs +60 -0
  29. package/web-ui/logic.runtime.mjs +11 -7
  30. package/web-ui/logic.sessions.mjs +372 -21
  31. package/web-ui/modules/api.mjs +22 -1
  32. package/web-ui/modules/app.computed.dashboard.mjs +23 -10
  33. package/web-ui/modules/app.computed.index.mjs +4 -0
  34. package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
  35. package/web-ui/modules/app.computed.session.mjs +521 -9
  36. package/web-ui/modules/app.methods.agents.mjs +62 -11
  37. package/web-ui/modules/app.methods.codex-config.mjs +189 -34
  38. package/web-ui/modules/app.methods.index.mjs +7 -1
  39. package/web-ui/modules/app.methods.install.mjs +24 -20
  40. package/web-ui/modules/app.methods.navigation.mjs +142 -1
  41. package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
  42. package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
  43. package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
  44. package/web-ui/modules/app.methods.providers.mjs +192 -53
  45. package/web-ui/modules/app.methods.session-actions.mjs +99 -19
  46. package/web-ui/modules/app.methods.session-browser.mjs +196 -5
  47. package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
  48. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  49. package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
  50. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  51. package/web-ui/modules/config-mode.computed.mjs +2 -0
  52. package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
  53. package/web-ui/modules/i18n.mjs +1609 -0
  54. package/web-ui/modules/plugins.computed.mjs +220 -0
  55. package/web-ui/modules/plugins.methods.mjs +620 -0
  56. package/web-ui/modules/plugins.storage.mjs +37 -0
  57. package/web-ui/partials/index/layout-footer.html +1 -57
  58. package/web-ui/partials/index/layout-header.html +299 -175
  59. package/web-ui/partials/index/modal-config-template-agents.html +79 -29
  60. package/web-ui/partials/index/modal-confirm-toast.html +1 -1
  61. package/web-ui/partials/index/modal-health-check.html +14 -14
  62. package/web-ui/partials/index/modal-openclaw-config.html +47 -42
  63. package/web-ui/partials/index/modal-skills.html +130 -114
  64. package/web-ui/partials/index/modals-basic.html +71 -102
  65. package/web-ui/partials/index/panel-config-claude.html +50 -12
  66. package/web-ui/partials/index/panel-config-codex.html +34 -37
  67. package/web-ui/partials/index/panel-config-openclaw.html +10 -16
  68. package/web-ui/partials/index/panel-docs.html +147 -0
  69. package/web-ui/partials/index/panel-market.html +38 -38
  70. package/web-ui/partials/index/panel-orchestration.html +397 -0
  71. package/web-ui/partials/index/panel-plugins.html +243 -0
  72. package/web-ui/partials/index/panel-sessions.html +51 -146
  73. package/web-ui/partials/index/panel-settings.html +188 -96
  74. package/web-ui/partials/index/panel-usage.html +353 -0
  75. package/web-ui/session-helpers.mjs +221 -10
  76. package/web-ui/styles/base-theme.css +120 -229
  77. package/web-ui/styles/controls-forms.css +59 -51
  78. package/web-ui/styles/docs-panel.css +247 -0
  79. package/web-ui/styles/layout-shell.css +394 -128
  80. package/web-ui/styles/modals-core.css +18 -3
  81. package/web-ui/styles/navigation-panels.css +184 -183
  82. package/web-ui/styles/plugins-panel.css +518 -0
  83. package/web-ui/styles/responsive.css +102 -62
  84. package/web-ui/styles/sessions-list.css +13 -27
  85. package/web-ui/styles/sessions-preview.css +13 -7
  86. package/web-ui/styles/sessions-toolbar-trash.css +25 -0
  87. package/web-ui/styles/sessions-usage.css +581 -6
  88. package/web-ui/styles/settings-panel.css +166 -0
  89. package/web-ui/styles/skills-list.css +16 -11
  90. package/web-ui/styles/skills-market.css +63 -2
  91. package/web-ui/styles/task-orchestration.css +776 -0
  92. package/web-ui/styles/titles-cards.css +67 -66
  93. package/web-ui/styles.css +4 -0
  94. package/README.en.md +0 -259
  95. package/res/screenshot.png +0 -0
  96. package/res/vue.global.js +0 -18552
@@ -0,0 +1,353 @@
1
+ <!-- Usage 统计 -->
2
+ <div
3
+ v-show="mainTab === 'usage'"
4
+ class="mode-content"
5
+ id="panel-usage"
6
+ role="tabpanel"
7
+ :aria-labelledby="'tab-usage'">
8
+ <div class="usage-toolbar">
9
+ <div class="selector-header" style="padding:0;border:0;background:none;">
10
+ <span class="selector-title">{{ t('usage.overview') }}</span>
11
+ </div>
12
+ <div class="usage-range-group" role="group" :aria-label="t('usage.range.aria')">
13
+ <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '7d' }" @click="sessionsUsageTimeRange = '7d'">{{ t('usage.range.7d') }}</button>
14
+ <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '30d' }" @click="sessionsUsageTimeRange = '30d'">{{ t('usage.range.30d') }}</button>
15
+ <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === 'all' }" @click="sessionsUsageTimeRange = 'all'">{{ t('usage.range.all') }}</button>
16
+ <button type="button" class="usage-range-btn" @click="loadSessionsUsage({ forceRefresh: true })" :disabled="sessionsUsageLoading">{{ sessionsUsageLoading ? t('usage.refreshing') : t('usage.refresh') }}</button>
17
+ </div>
18
+ </div>
19
+
20
+ <div v-if="sessionsUsageLoading && !sessionsUsageList.length" class="session-empty">{{ t('usage.loading') }}</div>
21
+ <div v-if="sessionsUsageLoading && !sessionsUsageList.length" class="usage-loading-skeleton" aria-busy="true">
22
+ <div class="usage-summary-grid">
23
+ <div v-for="n in 8" :key="'usage-skeleton-card-' + n" class="usage-summary-card usage-skeleton-card">
24
+ <div class="usage-skeleton-line w-40"></div>
25
+ <div class="usage-skeleton-line w-70 h-lg"></div>
26
+ <div class="usage-skeleton-line w-55"></div>
27
+ </div>
28
+ </div>
29
+ <div class="usage-chart-grid">
30
+ <section class="usage-card usage-card-wide usage-skeleton-block"></section>
31
+ <section class="usage-card usage-skeleton-block"></section>
32
+ <section class="usage-card usage-skeleton-block"></section>
33
+ </div>
34
+ </div>
35
+ <div v-else-if="sessionsUsageError && !sessionsUsageList.length" class="usage-empty">{{ sessionsUsageError }}</div>
36
+ <div v-else-if="!sessionsUsageList.length" class="usage-empty">{{ t('usage.empty') }}</div>
37
+ <template v-else>
38
+ <div class="usage-content" :class="{ 'usage-content-loading': sessionsUsageLoading }">
39
+ <div v-if="sessionsUsageLoading" class="usage-content-overlay" aria-live="polite">
40
+ <span class="usage-spinner" aria-hidden="true"></span>
41
+ {{ t('usage.refreshOverlay') }}
42
+ </div>
43
+
44
+ <div class="usage-summary-grid">
45
+ <div
46
+ v-for="card in sessionUsageSummaryCards"
47
+ :key="card.key"
48
+ class="usage-summary-card usage-copyable"
49
+ role="button"
50
+ tabindex="0"
51
+ :title="(card.title || card.value) ? t('usage.copyTitle', { value: (card.title || card.value) }) : null"
52
+ @click="(String(card.title || card.value || '').trim())
53
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(card.title || card.value || '').trim()))
54
+ ? showMessage(t('usage.copySuccess', { label: card.label }), 'success')
55
+ : showMessage(t('usage.copyFail'), 'error'))
56
+ : showMessage(t('usage.copyNone'), 'info')"
57
+ @keydown.enter.prevent="(String(card.title || card.value || '').trim())
58
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(card.title || card.value || '').trim()))
59
+ ? showMessage(t('usage.copySuccess', { label: card.label }), 'success')
60
+ : showMessage(t('usage.copyFail'), 'error'))
61
+ : showMessage(t('usage.copyNone'), 'info')"
62
+ @keydown.space.prevent="(String(card.title || card.value || '').trim())
63
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(card.title || card.value || '').trim()))
64
+ ? showMessage(t('usage.copySuccess', { label: card.label }), 'success')
65
+ : showMessage(t('usage.copyFail'), 'error'))
66
+ : showMessage(t('usage.copyNone'), 'info')">
67
+ <div class="usage-summary-label">{{ card.label }}</div>
68
+ <div class="usage-summary-value" :title="card.title || null">{{ card.value }}</div>
69
+ <div v-if="card.note" class="usage-summary-note">{{ card.note }}</div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="usage-chart-grid">
74
+ <section class="usage-card usage-card-wide usage-card-daily">
75
+ <div class="usage-card-head">
76
+ <div>
77
+ <div class="usage-card-title">{{ t('usage.daily.title') }}</div>
78
+ <div class="usage-card-subtitle">{{ t('usage.daily.subtitle') }}</div>
79
+ </div>
80
+ <div class="usage-card-kicker">{{ sessionsUsageTimeRange === 'all' ? t('usage.range.kicker.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.kicker.30d') : t('usage.range.kicker.7d')) }}</div>
81
+ </div>
82
+
83
+ <div class="usage-daily-legend">
84
+ <span><span class="usage-legend-dot usage-daily-dot usage-daily-dot-tokens"></span>{{ t('usage.legend.tokens') }}</span>
85
+ <span><span class="usage-legend-dot usage-daily-dot usage-daily-dot-cost"></span>{{ t('usage.legend.cost') }}</span>
86
+ </div>
87
+
88
+ <div class="usage-daily-bars usage-bars">
89
+ <div v-for="day in (sessionUsageDaily && sessionUsageDaily.rows ? sessionUsageDaily.rows : [])" :key="day.key" class="usage-bar-group">
90
+ <div class="usage-daily-bar-stack" :title="`${day.key} · ${day.tokenTitle} token · ${day.costTitle}`">
91
+ <div class="usage-daily-bar usage-daily-bar-tokens" :style="{ height: day.tokenPercent + '%' }"></div>
92
+ <div class="usage-daily-bar usage-daily-bar-cost" :style="{ height: day.costPercent + '%' }"></div>
93
+ </div>
94
+ <div class="usage-bar-label">{{ day.label }}</div>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="usage-daily-table-wrap">
99
+ <table class="usage-daily-table">
100
+ <thead>
101
+ <tr>
102
+ <th>{{ t('usage.table.date') }}</th>
103
+ <th class="right">{{ t('usage.table.sessions') }}</th>
104
+ <th class="right">{{ t('usage.table.messages') }}</th>
105
+ <th class="right">{{ t('usage.table.tokens') }}</th>
106
+ <th class="right">{{ t('usage.table.cost') }}</th>
107
+ </tr>
108
+ </thead>
109
+ <tbody>
110
+ <tr v-for="day in sessionUsageDailyTableRows" :key="day.key">
111
+ <td class="mono">{{ day.key }}</td>
112
+ <td class="right">{{ day.sessionCount }}</td>
113
+ <td class="right">{{ day.messageCount }}</td>
114
+ <td
115
+ class="right mono usage-copyable"
116
+ role="button"
117
+ tabindex="0"
118
+ :title="t('usage.copyTitle', { value: day.tokenTitle })"
119
+ @click="(String(day.tokenTitle || '').trim())
120
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
121
+ ? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
122
+ : showMessage(t('usage.copyFail'), 'error'))
123
+ : showMessage(t('usage.copyNone'), 'info')"
124
+ @keydown.enter.prevent="(String(day.tokenTitle || '').trim())
125
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
126
+ ? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
127
+ : showMessage(t('usage.copyFail'), 'error'))
128
+ : showMessage(t('usage.copyNone'), 'info')"
129
+ @keydown.space.prevent="(String(day.tokenTitle || '').trim())
130
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
131
+ ? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
132
+ : showMessage(t('usage.copyFail'), 'error'))
133
+ : showMessage(t('usage.copyNone'), 'info')">
134
+ {{ day.tokenLabel }}
135
+ </td>
136
+ <td
137
+ class="right mono usage-copyable"
138
+ role="button"
139
+ tabindex="0"
140
+ :title="t('usage.copyTitle', { value: day.costTitle })"
141
+ @click="(String(day.costTitle || '').trim())
142
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
143
+ ? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
144
+ : showMessage(t('usage.copyFail'), 'error'))
145
+ : showMessage(t('usage.copyNone'), 'info')"
146
+ @keydown.enter.prevent="(String(day.costTitle || '').trim())
147
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
148
+ ? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
149
+ : showMessage(t('usage.copyFail'), 'error'))
150
+ : showMessage(t('usage.copyNone'), 'info')"
151
+ @keydown.space.prevent="(String(day.costTitle || '').trim())
152
+ ? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
153
+ ? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
154
+ : showMessage(t('usage.copyFail'), 'error'))
155
+ : showMessage(t('usage.copyNone'), 'info')">
156
+ {{ day.costLabel }}
157
+ </td>
158
+ </tr>
159
+ </tbody>
160
+ </table>
161
+ </div>
162
+
163
+ <div class="usage-daily-note">
164
+ {{ t('usage.daily.note') }}
165
+ </div>
166
+ </section>
167
+
168
+ <section class="usage-card">
169
+ <div class="usage-card-title">{{ t('usage.trend.sessions') }}</div>
170
+ <div class="usage-legend">
171
+ <span><span class="usage-legend-dot" style="background:#4f8cff"></span>{{ t('usage.legend.codex') }}</span>
172
+ <span><span class="usage-legend-dot" style="background:#b277ff"></span>{{ t('usage.legend.claude') }}</span>
173
+ </div>
174
+ <div class="usage-bars">
175
+ <div v-for="bucket in sessionUsageCharts.buckets" :key="bucket.key" class="usage-bar-group">
176
+ <div class="usage-bar-stack">
177
+ <div class="usage-bar codex" :style="{ height: ((bucket.codex / Math.max(sessionUsageCharts.maxSessionBucket, 1)) * 100) + '%' }" :title="t('usage.trend.sessions.codexTitle', { count: bucket.codex })"></div>
178
+ <div class="usage-bar claude" :style="{ height: ((bucket.claude / Math.max(sessionUsageCharts.maxSessionBucket, 1)) * 100) + '%' }" :title="t('usage.trend.sessions.claudeTitle', { count: bucket.claude })"></div>
179
+ </div>
180
+ <div class="usage-bar-label">{{ bucket.label }}</div>
181
+ </div>
182
+ </div>
183
+ </section>
184
+
185
+ <section class="usage-card">
186
+ <div class="usage-card-title">{{ t('usage.trend.messages') }}</div>
187
+ <div class="usage-bars">
188
+ <div v-for="bucket in sessionUsageCharts.buckets" :key="bucket.key + '-messages'" class="usage-bar-group">
189
+ <div class="usage-bar-stack">
190
+ <div class="usage-bar codex" style="width:100%" :style="{ height: ((bucket.totalMessages / Math.max(sessionUsageCharts.maxMessageBucket, 1)) * 100) + '%' }" :title="t('usage.trend.messages.barTitle', { count: bucket.totalMessages })"></div>
191
+ </div>
192
+ <div class="usage-bar-label">{{ bucket.label }}</div>
193
+ </div>
194
+ </div>
195
+ </section>
196
+
197
+ <section class="usage-card">
198
+ <div class="usage-card-title">{{ t('usage.trend.activeHours') }}</div>
199
+ <div class="usage-mini-bars">
200
+ <div v-for="item in sessionUsageCharts.hourActivity" :key="item.key" class="usage-mini-bar-group">
201
+ <div class="usage-mini-bar-track" :title="t('usage.hour.title', { hour: item.label, count: item.count })">
202
+ <div class="usage-mini-bar-fill" :style="{ height: item.percent + '%' }"></div>
203
+ </div>
204
+ <div class="usage-mini-bar-label">{{ item.label }}</div>
205
+ </div>
206
+ </div>
207
+ </section>
208
+
209
+ <section class="usage-card">
210
+ <div class="usage-card-title">{{ t('usage.trend.sources') }}</div>
211
+ <div class="usage-list">
212
+ <div v-for="item in sessionUsageCharts.sourceShare" :key="item.key" class="usage-list-row">
213
+ <div class="usage-list-label">{{ item.label }}</div>
214
+ <div class="usage-progress"><div class="usage-progress-fill" :style="{ width: item.percent + '%' }"></div></div>
215
+ <div class="usage-list-value">{{ item.percent }}%</div>
216
+ <div class="usage-list-subvalue">{{ t('usage.source.row', { sessions: item.value, messages: item.messageTotal, avg: item.avgMessages }) }}</div>
217
+ </div>
218
+ </div>
219
+ </section>
220
+
221
+ <section class="usage-card usage-card-models">
222
+ <div class="usage-card-head">
223
+ <div>
224
+ <div class="usage-card-title">{{ t('usage.models.title') }}</div>
225
+ <div class="usage-card-subtitle">{{ t('usage.models.subtitle') }}</div>
226
+ </div>
227
+ <div class="usage-card-kicker">{{ t('usage.models.kicker', { modeled: sessionUsageCharts.modelCoverage.modeledSessions, total: sessionUsageCharts.modelCoverage.totalSessions }) }}</div>
228
+ </div>
229
+ <div class="usage-model-coverage-strip">
230
+ <div class="usage-model-coverage-item">
231
+ <strong>{{ sessionUsageCharts.usedModels.length }}</strong>
232
+ <span>{{ t('usage.models.count') }}</span>
233
+ </div>
234
+ <div class="usage-model-coverage-item">
235
+ <strong>{{ sessionUsageCharts.modelCoverage.coveragePercent }}%</strong>
236
+ <span>{{ t('usage.models.coverage') }}</span>
237
+ </div>
238
+ <div class="usage-model-coverage-item">
239
+ <strong>{{ sessionUsageCharts.modelCoverage.missingModelSessions }}</strong>
240
+ <span>{{ t('usage.models.missing') }}</span>
241
+ </div>
242
+ </div>
243
+ <div v-if="!sessionUsageCharts.usedModels.length" class="usage-diagnostic-empty">
244
+ <div class="usage-diagnostic-title">{{ t('usage.models.noneTitle') }}</div>
245
+ <div class="usage-diagnostic-copy">
246
+ {{ t('usage.models.noneBody', { total: sessionUsageCharts.modelCoverage.totalSessions }) }}
247
+ </div>
248
+ <div v-if="sessionUsageCharts.modelCoverage.providerOnlySessions > 0" class="usage-diagnostic-meta">
249
+ {{ t('usage.models.providerOnly', { count: sessionUsageCharts.modelCoverage.providerOnlySessions }) }}
250
+ </div>
251
+ </div>
252
+ <template v-else>
253
+ <div v-if="sessionUsageCharts.modelCoverage.providerOnlySessions > 0" class="usage-inline-note">
254
+ {{ t('usage.models.missingNote.providerOnly', { count: sessionUsageCharts.modelCoverage.missingModelSessions }) }}
255
+ </div>
256
+ <div v-else-if="sessionUsageCharts.modelCoverage.missingModelSessions > 0" class="usage-inline-note">
257
+ {{ t('usage.models.missingNote', { count: sessionUsageCharts.modelCoverage.missingModelSessions }) }}
258
+ </div>
259
+ <div v-if="sessionUsageCharts.modelCoverage.missingModelSessionsPreview.length" class="usage-diagnostic-list">
260
+ <div class="usage-diagnostic-list-title">{{ t('usage.models.missingListTitle') }}</div>
261
+ <div
262
+ v-for="item in sessionUsageCharts.modelCoverage.missingModelSessionsPreview"
263
+ :key="item.key"
264
+ class="usage-diagnostic-row">
265
+ <div class="usage-diagnostic-row-main">
266
+ <div class="usage-diagnostic-row-title">{{ item.title }}</div>
267
+ <div class="usage-diagnostic-row-meta">
268
+ <span v-if="item.sessionId">{{ item.sessionId }}</span>
269
+ <span v-else-if="item.updatedAtLabel">{{ item.updatedAtLabel }}</span>
270
+ </div>
271
+ </div>
272
+ <div class="usage-diagnostic-row-side">
273
+ <span v-if="item.provider" class="usage-inline-tag">{{ item.provider }}</span>
274
+ <span class="usage-inline-stat">{{ item.reasonLabel }}</span>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ <div class="usage-model-list">
279
+ <div
280
+ v-for="item in sessionUsageCharts.usedModels"
281
+ :key="item.key"
282
+ class="usage-model-chip"
283
+ :title="t('usage.models.chipTitle', { model: item.model, sessions: item.count, messages: item.messageTotal, tokens: (item.tokenTotal ? (' · ' + item.tokenTotal + ' token') : '') })">
284
+ <div class="usage-model-name">{{ item.model }}</div>
285
+ <div class="usage-model-meta">{{ t('usage.models.meta', { sessions: item.count, messages: item.messageTotal, tokens: (item.tokenTotal ? (' · ' + item.tokenTotal.toLocaleString('en-US') + ' token') : '') }) }}</div>
286
+ </div>
287
+ </div>
288
+ </template>
289
+ </section>
290
+
291
+ <section class="usage-card">
292
+ <div class="usage-card-title">{{ t('usage.weekday.title') }}</div>
293
+ <div class="usage-list">
294
+ <div v-for="item in sessionUsageCharts.weekdayActivity" :key="item.key" class="usage-list-row usage-list-row-compact">
295
+ <div class="usage-list-label">{{ item.label }}</div>
296
+ <div class="usage-progress"><div class="usage-progress-fill" :style="{ width: item.percent + '%' }"></div></div>
297
+ <div class="usage-list-value">{{ item.count }}</div>
298
+ </div>
299
+ </div>
300
+ </section>
301
+
302
+ <section class="usage-card">
303
+ <div class="usage-card-title">{{ t('usage.paths.title') }}</div>
304
+ <div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
305
+ <div v-else class="usage-list">
306
+ <div v-for="item in sessionUsageCharts.topPaths" :key="item.path" class="usage-list-row">
307
+ <div class="usage-list-label">{{ t('usage.paths.count', { count: item.count }) }}</div>
308
+ <div class="usage-progress"><div class="usage-progress-fill" :style="{ width: ((item.count / Math.max((sessionUsageCharts.topPaths.length ? sessionUsageCharts.topPaths[0].count : 1), 1)) * 100) + '%' }"></div></div>
309
+ <div class="usage-list-value" :title="item.path">{{ item.path }}</div>
310
+ <div class="usage-list-subvalue">{{ t('usage.paths.meta', { messages: item.messageTotal, recent: (item.updatedAtLabel ? t('usage.paths.recent', { label: item.updatedAtLabel }) : '') }) }}</div>
311
+ </div>
312
+ </div>
313
+ </section>
314
+
315
+ <section class="usage-card">
316
+ <div class="usage-card-title">{{ t('usage.recent.title') }}</div>
317
+ <div v-if="!sessionUsageCharts.recentSessions.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
318
+ <div v-else class="usage-session-list">
319
+ <div v-for="item in sessionUsageCharts.recentSessions" :key="item.key" class="usage-session-item">
320
+ <div class="usage-session-row">
321
+ <div class="usage-session-title" :title="item.title">{{ item.title }}</div>
322
+ <span :class="['pill', item.source === 'codex' ? 'configured' : 'empty']">{{ item.sourceLabel }}</span>
323
+ </div>
324
+ <div class="usage-session-meta">
325
+ <span>{{ t('usage.sessions.messages', { count: item.messageCount }) }}</span>
326
+ <span v-if="item.updatedAtLabel">{{ item.updatedAtLabel }}</span>
327
+ </div>
328
+ <div v-if="item.cwd" class="usage-session-path" :title="item.cwd">{{ item.cwd }}</div>
329
+ </div>
330
+ </div>
331
+ </section>
332
+
333
+ <section class="usage-card">
334
+ <div class="usage-card-title">{{ t('usage.sessions.topDensity') }}</div>
335
+ <div v-if="!sessionUsageCharts.topSessionsByMessages.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
336
+ <div v-else class="usage-session-list">
337
+ <div v-for="item in sessionUsageCharts.topSessionsByMessages" :key="item.key + '-messages'" class="usage-session-item">
338
+ <div class="usage-session-row">
339
+ <div class="usage-session-title" :title="item.title">{{ item.title }}</div>
340
+ <div class="usage-inline-stat">{{ t('usage.sessions.messages', { count: item.messageCount }) }}</div>
341
+ </div>
342
+ <div class="usage-session-meta">
343
+ <span>{{ item.sourceLabel }}</span>
344
+ <span v-if="item.updatedAtLabel">{{ item.updatedAtLabel }}</span>
345
+ </div>
346
+ <div v-if="item.cwd" class="usage-session-path" :title="item.cwd">{{ item.cwd }}</div>
347
+ </div>
348
+ </div>
349
+ </section>
350
+ </div>
351
+ </div>
352
+ </template>
353
+ </div>