codexmate 0.0.25 → 0.0.27
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 +11 -3
- package/README.zh.md +10 -2
- package/cli/builtin-proxy.js +315 -95
- package/cli/openai-bridge.js +99 -5
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli.js +547 -32
- package/package.json +74 -74
- package/web-ui/app.js +24 -2
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +151 -0
- package/web-ui/modules/app.computed.dashboard.mjs +44 -1
- package/web-ui/modules/app.computed.session.mjs +336 -12
- package/web-ui/modules/app.methods.claude-config.mjs +11 -1
- package/web-ui/modules/app.methods.codex-config.mjs +76 -0
- package/web-ui/modules/app.methods.navigation.mjs +51 -3
- package/web-ui/modules/app.methods.session-actions.mjs +55 -3
- package/web-ui/modules/app.methods.session-browser.mjs +270 -3
- package/web-ui/modules/app.methods.session-timeline.mjs +34 -3
- package/web-ui/modules/app.methods.session-trash.mjs +16 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +234 -125
- package/web-ui/modules/i18n.dict.mjs +76 -0
- package/web-ui/partials/index/panel-config-claude.html +12 -4
- package/web-ui/partials/index/panel-sessions.html +33 -10
- package/web-ui/partials/index/panel-settings.html +16 -0
- package/web-ui/partials/index/panel-usage.html +95 -85
- package/web-ui/session-helpers.mjs +3 -0
- package/web-ui/styles/base-theme.css +29 -25
- package/web-ui/styles/layout-shell.css +1 -1
- package/web-ui/styles/navigation-panels.css +9 -9
- package/web-ui/styles/sessions-list.css +17 -0
- package/web-ui/styles/sessions-toolbar-trash.css +62 -0
- package/web-ui/styles/sessions-usage.css +211 -83
- package/web-ui/styles/settings-panel.css +19 -0
|
@@ -81,6 +81,14 @@
|
|
|
81
81
|
<option value="30d">{{ t('sessions.time.30d') }}</option>
|
|
82
82
|
<option value="90d">{{ t('sessions.time.90d') }}</option>
|
|
83
83
|
</select>
|
|
84
|
+
<select
|
|
85
|
+
class="session-time-select"
|
|
86
|
+
v-model="sessionSortMode"
|
|
87
|
+
@change="onSessionSortChange"
|
|
88
|
+
:disabled="sessionsLoading">
|
|
89
|
+
<option value="time">{{ t('sessions.sort.time') }}</option>
|
|
90
|
+
<option value="hot">{{ t('sessions.sort.hot') }}</option>
|
|
91
|
+
</select>
|
|
84
92
|
<button class="btn-tool btn-tool-compact" type="button" @click="copySessionsFilterShareUrl" :disabled="sessionsLoading">
|
|
85
93
|
{{ t('sessions.filters.copyLink') }}
|
|
86
94
|
</button>
|
|
@@ -99,6 +107,19 @@
|
|
|
99
107
|
{{ t('sessions.resumeYolo') }}
|
|
100
108
|
</label>
|
|
101
109
|
</div>
|
|
110
|
+
<div v-if="hasActiveSessionFilters()" class="session-filter-chips" :aria-label="t('sessions.filters.copyLink')">
|
|
111
|
+
<button
|
|
112
|
+
v-for="chip in getSessionFilterChips()"
|
|
113
|
+
:key="'chip-' + chip.key"
|
|
114
|
+
type="button"
|
|
115
|
+
class="session-filter-chip"
|
|
116
|
+
@click="clearSessionFilterChip(chip.key)"
|
|
117
|
+
:title="`${chip.title}: ${chip.value}`">
|
|
118
|
+
<span class="session-filter-chip-title">{{ chip.title }}</span>
|
|
119
|
+
<span class="session-filter-chip-value">{{ chip.value }}</span>
|
|
120
|
+
<span class="session-filter-chip-close">×</span>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
102
123
|
</div>
|
|
103
124
|
|
|
104
125
|
<div v-if="sessionsLoading" class="state-message">
|
|
@@ -118,7 +139,7 @@
|
|
|
118
139
|
<div
|
|
119
140
|
v-for="session in visibleSessionsList"
|
|
120
141
|
:key="session.source + '-' + session.sessionId + '-' + session.filePath"
|
|
121
|
-
v-memo="[activeSessionExportKey === getSessionExportKey(session), session.messageCount, session.updatedAt, session.title, session.sourceLabel, isSessionPinned(session), sessionsLoading]"
|
|
142
|
+
v-memo="[activeSessionExportKey === getSessionExportKey(session), session.messageCount, session.updatedAt, session.title, session.sourceLabel, session.cwd, isSessionPinned(session), sessionsLoading]"
|
|
122
143
|
:class="[
|
|
123
144
|
'session-item',
|
|
124
145
|
{
|
|
@@ -135,7 +156,7 @@
|
|
|
135
156
|
<div class="session-item-header">
|
|
136
157
|
<div class="session-item-main">
|
|
137
158
|
<div class="session-item-title">{{ session.title || session.sessionId }}</div>
|
|
138
|
-
<span class="session-count-badge">{{ session.messageCount == null ?
|
|
159
|
+
<span class="session-count-badge">{{ session.messageCount == null ? '...' : session.messageCount }}</span>
|
|
139
160
|
</div>
|
|
140
161
|
<div class="session-item-actions">
|
|
141
162
|
<button
|
|
@@ -169,6 +190,8 @@
|
|
|
169
190
|
<div class="session-item-meta">
|
|
170
191
|
<span class="session-source">{{ session.sourceLabel }}</span>
|
|
171
192
|
<span class="session-item-time">{{ session.updatedAt || t('sessions.unknownTime') }}</span>
|
|
193
|
+
<span v-if="getSessionHotLabel(session)" class="session-item-hot">{{ getSessionHotLabel(session) }}</span>
|
|
194
|
+
<span v-if="session.cwd" class="session-item-cwd session-item-sub">{{ session.cwd }}</span>
|
|
172
195
|
</div>
|
|
173
196
|
</div>
|
|
174
197
|
</div>
|
|
@@ -205,6 +228,12 @@
|
|
|
205
228
|
:disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]">
|
|
206
229
|
{{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? t('sessions.preview.exporting') : t('sessions.preview.export') }}
|
|
207
230
|
</button>
|
|
231
|
+
<button
|
|
232
|
+
class="btn-session-export"
|
|
233
|
+
@click="convertSession(activeSession)"
|
|
234
|
+
:disabled="true">
|
|
235
|
+
{{ (activeSession && sessionConverting[getSessionExportKey(activeSession)]) ? t('sessions.preview.converting') : t('sessions.preview.convert') }}
|
|
236
|
+
</button>
|
|
208
237
|
<button
|
|
209
238
|
class="btn-session-open"
|
|
210
239
|
@click="openSessionStandalone(activeSession)"
|
|
@@ -247,15 +276,9 @@
|
|
|
247
276
|
class="session-item-sub session-item-wrap"
|
|
248
277
|
style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
249
278
|
<span>{{ t('sessions.preview.shownCount', { shown: activeSessionVisibleMessages.length, total: activeSessionMessages.length }) }}</span>
|
|
250
|
-
<
|
|
251
|
-
{{ sessionPreviewLoadingMore ? t('sessions.preview.loading') : t('sessions.preview.loadMore', { remain: sessionPreviewRemainingCount }) }}
|
|
252
|
-
</button>
|
|
253
|
-
</div>
|
|
254
|
-
<div
|
|
255
|
-
v-if="sessionPreviewLoadingMore"
|
|
256
|
-
class="session-item-sub session-item-wrap">
|
|
257
|
-
{{ t('sessions.preview.loadingMore') }}
|
|
279
|
+
<span>{{ t('sessions.preview.loadMore', { remain: sessionPreviewRemainingCount }) }}</span>
|
|
258
280
|
</div>
|
|
281
|
+
<div v-if="sessionPreviewLoadingMore" class="session-item-sub session-item-wrap">{{ t('sessions.preview.loadingMore') }}</div>
|
|
259
282
|
<div
|
|
260
283
|
v-for="(msg, idx) in activeSessionVisibleMessages"
|
|
261
284
|
:key="getRecordRenderKey(msg, idx)"
|
|
@@ -141,6 +141,22 @@
|
|
|
141
141
|
</div>
|
|
142
142
|
</section>
|
|
143
143
|
|
|
144
|
+
<section class="settings-card settings-card--wide" :aria-label="t('settings.trash.retention')">
|
|
145
|
+
<div class="settings-card-header">
|
|
146
|
+
<div class="settings-card-title">{{ t('settings.trash.retention') }}</div>
|
|
147
|
+
<div class="settings-card-meta">{{ t('settings.trash.retentionMeta') }}</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="settings-card-body">
|
|
150
|
+
<label class="settings-retention-row">
|
|
151
|
+
<span>{{ t('settings.trash.retentionLabel') }}</span>
|
|
152
|
+
<input type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
|
|
153
|
+
</label>
|
|
154
|
+
<div class="settings-card-hint">
|
|
155
|
+
{{ t('settings.trash.retentionHint') }}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
144
160
|
<section class="settings-card settings-card--wide" :aria-label="t('settings.trash.title')">
|
|
145
161
|
<div class="settings-card-header settings-card-header-row">
|
|
146
162
|
<div>
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
<button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '7d' }" @click="setSessionsUsageTimeRange('7d')">{{ t('usage.range.7d') }}</button>
|
|
14
14
|
<button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '30d' }" @click="setSessionsUsageTimeRange('30d')">{{ t('usage.range.30d') }}</button>
|
|
15
15
|
<button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === 'all' }" @click="setSessionsUsageTimeRange('all')">{{ t('usage.range.all') }}</button>
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
class="usage-range-btn"
|
|
19
|
+
:class="{ active: sessionsUsageCompareEnabled }"
|
|
20
|
+
@click="toggleSessionsUsageCompare"
|
|
21
|
+
:disabled="sessionsUsageTimeRange === 'all'">
|
|
22
|
+
{{ t('usage.compare.toggle') }}
|
|
23
|
+
</button>
|
|
16
24
|
<button type="button" class="usage-range-btn" @click="loadSessionsUsage({ forceRefresh: true, range: sessionsUsageTimeRange })" :disabled="sessionsUsageLoading">{{ sessionsUsageLoading ? t('usage.refreshing') : t('usage.refresh') }}</button>
|
|
17
25
|
<button type="button" class="usage-range-btn" @click="switchMainTab('dashboard')">{{ t('dashboard.doctor.title') }}</button>
|
|
18
26
|
</div>
|
|
@@ -79,97 +87,69 @@
|
|
|
79
87
|
</div>
|
|
80
88
|
|
|
81
89
|
<div class="usage-chart-grid">
|
|
82
|
-
|
|
90
|
+
<section class="usage-card usage-card-daydetail">
|
|
83
91
|
<div class="usage-card-head">
|
|
84
92
|
<div>
|
|
85
|
-
<div class="usage-card-title">{{ t('usage.
|
|
86
|
-
<div class="usage-card-subtitle">{{ t('usage.
|
|
93
|
+
<div class="usage-card-title">{{ t('usage.dayDetail.title', { day: sessionsUsageSelectedDaySummary ? sessionsUsageSelectedDaySummary.dayKey : '--' }) }}</div>
|
|
94
|
+
<div class="usage-card-subtitle">{{ t('usage.dayDetail.subtitle') }}</div>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="usage-daydetail-controls">
|
|
97
|
+
<select
|
|
98
|
+
class="usage-daydetail-select"
|
|
99
|
+
:value="sessionsUsageSelectedDayKey || (sessionUsageDailyTableRows[0] && sessionUsageDailyTableRows[0].key) || ''"
|
|
100
|
+
@change="selectSessionsUsageDay(($event.target && $event.target.value) ? String($event.target.value) : '')">
|
|
101
|
+
<option v-for="day in sessionUsageDailyTableRows" :key="'day-select-' + day.key" :value="day.key">{{ day.key }}</option>
|
|
102
|
+
</select>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
class="btn-tool btn-tool-compact"
|
|
106
|
+
:disabled="!sessionsUsageSelectedDaySummary"
|
|
107
|
+
@click="clearSessionsUsageDay">{{ t('usage.dayDetail.clear') }}</button>
|
|
87
108
|
</div>
|
|
88
|
-
<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>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div class="usage-daily-legend">
|
|
92
|
-
<span><span class="usage-legend-dot usage-daily-dot usage-daily-dot-tokens"></span>{{ t('usage.legend.tokens') }}</span>
|
|
93
|
-
<span><span class="usage-legend-dot usage-daily-dot usage-daily-dot-cost"></span>{{ t('usage.legend.cost') }}</span>
|
|
94
109
|
</div>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<div class="usage-
|
|
100
|
-
|
|
110
|
+
<template v-if="sessionsUsageSelectedDaySummary">
|
|
111
|
+
<div class="usage-daydetail-grid">
|
|
112
|
+
<div class="usage-daydetail-metric">
|
|
113
|
+
<div class="usage-daydetail-label">{{ t('usage.table.sessions') }}</div>
|
|
114
|
+
<div class="usage-daydetail-value">{{ sessionsUsageSelectedDaySummary.sessionCount }}</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="usage-daydetail-metric">
|
|
117
|
+
<div class="usage-daydetail-label">{{ t('usage.table.messages') }}</div>
|
|
118
|
+
<div class="usage-daydetail-value">{{ sessionsUsageSelectedDaySummary.messageCount }}</div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="usage-daydetail-metric">
|
|
121
|
+
<div class="usage-daydetail-label">{{ t('usage.table.tokens') }}</div>
|
|
122
|
+
<div class="usage-daydetail-value mono">{{ sessionsUsageSelectedDaySummary.tokenLabel }}</div>
|
|
123
|
+
<div v-if="sessionsUsageSelectedDaySummary.compareEnabled" class="usage-daydetail-sub mono">
|
|
124
|
+
{{ t('usage.compare.prev') }} {{ sessionsUsageSelectedDaySummary.prevTokenLabel }} · {{ t('usage.compare.delta') }} {{ sessionsUsageSelectedDaySummary.deltaTokenLabel }}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="usage-daydetail-metric">
|
|
128
|
+
<div class="usage-daydetail-label">{{ t('usage.table.cost') }}</div>
|
|
129
|
+
<div class="usage-daydetail-value mono">{{ sessionsUsageSelectedDaySummary.costLabel }}</div>
|
|
130
|
+
<div v-if="sessionsUsageSelectedDaySummary.compareEnabled" class="usage-daydetail-sub mono">
|
|
131
|
+
{{ t('usage.compare.prev') }} {{ sessionsUsageSelectedDaySummary.prevCostLabel }} · {{ t('usage.compare.delta') }} {{ sessionsUsageSelectedDaySummary.deltaCostLabel }}
|
|
132
|
+
</div>
|
|
101
133
|
</div>
|
|
102
|
-
<div class="usage-bar-label">{{ day.label }}</div>
|
|
103
134
|
</div>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<td
|
|
123
|
-
class="right mono usage-copyable"
|
|
124
|
-
role="button"
|
|
125
|
-
tabindex="0"
|
|
126
|
-
:title="t('usage.copyTitle', { value: day.tokenTitle })"
|
|
127
|
-
@click="(String(day.tokenTitle || '').trim())
|
|
128
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
|
|
129
|
-
? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
|
|
130
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
131
|
-
: showMessage(t('usage.copyNone'), 'info')"
|
|
132
|
-
@keydown.enter.prevent="(String(day.tokenTitle || '').trim())
|
|
133
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
|
|
134
|
-
? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
|
|
135
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
136
|
-
: showMessage(t('usage.copyNone'), 'info')"
|
|
137
|
-
@keydown.space.prevent="(String(day.tokenTitle || '').trim())
|
|
138
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.tokenTitle || '').trim()))
|
|
139
|
-
? showMessage(t('usage.copyTokenDay', { day: day.key }), 'success')
|
|
140
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
141
|
-
: showMessage(t('usage.copyNone'), 'info')">
|
|
142
|
-
{{ day.tokenLabel }}
|
|
143
|
-
</td>
|
|
144
|
-
<td
|
|
145
|
-
class="right mono usage-copyable"
|
|
146
|
-
role="button"
|
|
147
|
-
tabindex="0"
|
|
148
|
-
:title="t('usage.copyTitle', { value: day.costTitle })"
|
|
149
|
-
@click="(String(day.costTitle || '').trim())
|
|
150
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
|
|
151
|
-
? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
|
|
152
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
153
|
-
: showMessage(t('usage.copyNone'), 'info')"
|
|
154
|
-
@keydown.enter.prevent="(String(day.costTitle || '').trim())
|
|
155
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
|
|
156
|
-
? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
|
|
157
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
158
|
-
: showMessage(t('usage.copyNone'), 'info')"
|
|
159
|
-
@keydown.space.prevent="(String(day.costTitle || '').trim())
|
|
160
|
-
? ((typeof fallbackCopyText === 'function' && fallbackCopyText(String(day.costTitle || '').trim()))
|
|
161
|
-
? showMessage(t('usage.copyCostDay', { day: day.key }), 'success')
|
|
162
|
-
: showMessage(t('usage.copyFail'), 'error'))
|
|
163
|
-
: showMessage(t('usage.copyNone'), 'info')">
|
|
164
|
-
{{ day.costLabel }}
|
|
165
|
-
</td>
|
|
166
|
-
</tr>
|
|
167
|
-
</tbody>
|
|
168
|
-
</table>
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<div class="usage-daily-note">
|
|
172
|
-
{{ t('usage.daily.note') }}
|
|
135
|
+
<div class="usage-daydetail-section">
|
|
136
|
+
<div class="usage-daydetail-section-title">{{ t('usage.dayDetail.topSessions') }}</div>
|
|
137
|
+
<div class="usage-daydetail-list">
|
|
138
|
+
<div v-for="item in sessionsUsageSelectedDaySummary.topSessions" :key="item.key" class="usage-daydetail-row">
|
|
139
|
+
<div class="usage-daydetail-row-title">{{ item.title }}</div>
|
|
140
|
+
<div class="usage-daydetail-row-meta mono">{{ item.messageCountLabel }}</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div v-if="sessionsUsageSelectedDaySummary.topModels.length" class="usage-daydetail-section">
|
|
145
|
+
<div class="usage-daydetail-section-title">{{ t('usage.dayDetail.topModels') }}</div>
|
|
146
|
+
<div class="usage-daydetail-models">
|
|
147
|
+
<span v-for="model in sessionsUsageSelectedDaySummary.topModels" :key="model.key" class="usage-daydetail-model">{{ model.label }}</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
<div v-else class="usage-daydetail-empty">
|
|
152
|
+
{{ t('usage.dayDetail.empty') }}
|
|
173
153
|
</div>
|
|
174
154
|
</section>
|
|
175
155
|
|
|
@@ -307,6 +287,36 @@
|
|
|
307
287
|
</div>
|
|
308
288
|
</section>
|
|
309
289
|
|
|
290
|
+
<section class="usage-card usage-card-hourly-heatmap">
|
|
291
|
+
<div class="usage-card-title">{{ t('usage.hourlyHeatmap.title') }}</div>
|
|
292
|
+
<div class="usage-card-subtitle">{{ t('usage.hourlyHeatmap.subtitle') }}</div>
|
|
293
|
+
<div class="hourly-heatmap-wrapper">
|
|
294
|
+
<div class="hourly-heatmap-header">
|
|
295
|
+
<div class="hourly-heatmap-corner"></div>
|
|
296
|
+
<div v-for="h in sessionUsageHourlyHeatmap.hourLabels" :key="'hh-' + h" class="hourly-heatmap-hour-label">{{ h }}</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div v-for="(row, dayIndex) in sessionUsageHourlyHeatmap.rows" :key="'hd-' + dayIndex" class="hourly-heatmap-row">
|
|
299
|
+
<div class="hourly-heatmap-weekday-label">{{ row.weekday }}</div>
|
|
300
|
+
<div
|
|
301
|
+
v-for="(cell, hourIndex) in row.cells"
|
|
302
|
+
:key="'hc-' + dayIndex + '-' + hourIndex"
|
|
303
|
+
:class="['hourly-heatmap-cell', 'level-' + cell.level]"
|
|
304
|
+
:title="cell.tooltip"
|
|
305
|
+
:aria-label="cell.tooltip">
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="hourly-heatmap-legend">
|
|
309
|
+
<span class="hourly-heatmap-legend-label">{{ t('usage.hourlyHeatmap.legend.less') }}</span>
|
|
310
|
+
<span class="hourly-heatmap-cell level-0"></span>
|
|
311
|
+
<span class="hourly-heatmap-cell level-1"></span>
|
|
312
|
+
<span class="hourly-heatmap-cell level-2"></span>
|
|
313
|
+
<span class="hourly-heatmap-cell level-3"></span>
|
|
314
|
+
<span class="hourly-heatmap-cell level-4"></span>
|
|
315
|
+
<span class="hourly-heatmap-legend-label">{{ t('usage.hourlyHeatmap.legend.more') }}</span>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</section>
|
|
319
|
+
|
|
310
320
|
<section class="usage-card usage-card-top-paths">
|
|
311
321
|
<div class="usage-card-title">{{ t('usage.paths.title') }}</div>
|
|
312
322
|
<div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
|
|
@@ -422,6 +422,9 @@ export async function loadActiveSessionDetail(api, options = {}) {
|
|
|
422
422
|
if (res.sourceLabel) {
|
|
423
423
|
this.activeSession.sourceLabel = res.sourceLabel;
|
|
424
424
|
}
|
|
425
|
+
if (typeof res.derived === 'boolean') {
|
|
426
|
+
this.activeSession.derived = res.derived;
|
|
427
|
+
}
|
|
425
428
|
if (res.sessionId) {
|
|
426
429
|
this.activeSession.sessionId = res.sessionId;
|
|
427
430
|
if (!this.activeSession.title) {
|
|
@@ -4,30 +4,34 @@
|
|
|
4
4
|
设计系统 - Design Tokens
|
|
5
5
|
============================================ */
|
|
6
6
|
:root {
|
|
7
|
-
/*
|
|
7
|
+
/* 色彩系统:中性灰(更贴近 craft/shadcn 观感)+ 品牌强调 */
|
|
8
8
|
--color-brand: #C77462;
|
|
9
9
|
--color-brand-dark: #B45E4E;
|
|
10
|
-
--color-brand-light: rgba(199, 116, 98, 0.
|
|
11
|
-
--color-brand-subtle: rgba(199, 116, 98, 0.
|
|
10
|
+
--color-brand-light: rgba(199, 116, 98, 0.12);
|
|
11
|
+
--color-brand-subtle: rgba(199, 116, 98, 0.18);
|
|
12
12
|
|
|
13
|
-
--color-bg: #
|
|
14
|
-
--color-surface: #
|
|
15
|
-
--color-surface-alt: #
|
|
13
|
+
--color-bg: #FAFAFA;
|
|
14
|
+
--color-surface: #FFFFFF;
|
|
15
|
+
--color-surface-alt: #F4F4F5;
|
|
16
16
|
--color-surface-elevated: #FFFFFF;
|
|
17
|
-
--color-surface-tint: rgba(255,
|
|
18
|
-
--color-text-primary: #
|
|
19
|
-
--color-text-secondary: #
|
|
20
|
-
--color-text-tertiary: #
|
|
21
|
-
--color-text-muted: #
|
|
22
|
-
--color-border: #
|
|
23
|
-
--color-border-soft: rgba(
|
|
24
|
-
--color-border-strong: rgba(
|
|
17
|
+
--color-surface-tint: rgba(255, 255, 255, 0.92);
|
|
18
|
+
--color-text-primary: #18181B;
|
|
19
|
+
--color-text-secondary: #3F3F46;
|
|
20
|
+
--color-text-tertiary: #71717A;
|
|
21
|
+
--color-text-muted: #A1A1AA;
|
|
22
|
+
--color-border: #E4E4E7;
|
|
23
|
+
--color-border-soft: rgba(24, 24, 27, 0.12);
|
|
24
|
+
--color-border-strong: rgba(24, 24, 27, 0.24);
|
|
25
25
|
|
|
26
26
|
--color-success: #4B8B6A;
|
|
27
27
|
--color-error: #C44536;
|
|
28
28
|
|
|
29
29
|
--bg-warm-gradient:
|
|
30
|
-
linear-gradient(180deg, #
|
|
30
|
+
linear-gradient(180deg, #FAFAFA 0%, #FAFAFA 100%);
|
|
31
|
+
|
|
32
|
+
--color-bg-topbar-strong: rgba(250, 250, 250, 0.96);
|
|
33
|
+
--color-bg-topbar-soft: rgba(250, 250, 250, 0.9);
|
|
34
|
+
--color-bg-topbar-clear: rgba(250, 250, 250, 0);
|
|
31
35
|
|
|
32
36
|
/* 字体系统 */
|
|
33
37
|
--font-family-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
@@ -63,21 +67,21 @@
|
|
|
63
67
|
--radius-sm: 8px;
|
|
64
68
|
--radius-md: 10px;
|
|
65
69
|
--radius-lg: 12px;
|
|
66
|
-
--radius-xl:
|
|
70
|
+
--radius-xl: 16px;
|
|
67
71
|
--radius-full: 50px;
|
|
68
72
|
|
|
69
|
-
/* 阴影系统 -
|
|
70
|
-
--shadow-subtle: 0 1px 2px rgba(
|
|
71
|
-
--shadow-card: 0
|
|
72
|
-
--shadow-card-hover: 0 8px
|
|
73
|
-
--shadow-float: 0
|
|
74
|
-
--shadow-raised: 0 8px 18px rgba(
|
|
73
|
+
/* 阴影系统 - 更偏中性与克制 */
|
|
74
|
+
--shadow-subtle: 0 1px 2px rgba(24, 24, 27, 0.04);
|
|
75
|
+
--shadow-card: 0 1px 3px rgba(24, 24, 27, 0.06);
|
|
76
|
+
--shadow-card-hover: 0 8px 26px rgba(24, 24, 27, 0.12);
|
|
77
|
+
--shadow-float: 0 14px 36px rgba(24, 24, 27, 0.16);
|
|
78
|
+
--shadow-raised: 0 8px 18px rgba(24, 24, 27, 0.12);
|
|
75
79
|
--shadow-modal:
|
|
76
|
-
0
|
|
77
|
-
0
|
|
80
|
+
0 10px 30px rgba(24, 24, 27, 0.14),
|
|
81
|
+
0 28px 72px rgba(24, 24, 27, 0.12);
|
|
78
82
|
--shadow-input-focus:
|
|
79
83
|
0 0 0 3px var(--color-brand-light),
|
|
80
|
-
0 1px
|
|
84
|
+
0 1px 2px rgba(24, 24, 27, 0.06);
|
|
81
85
|
|
|
82
86
|
/* 动画 - 更细腻的曲线 */
|
|
83
87
|
--transition-instant: 100ms;
|
|
@@ -185,7 +185,7 @@ body::after {
|
|
|
185
185
|
content: "";
|
|
186
186
|
position: absolute;
|
|
187
187
|
inset: -12px -18px;
|
|
188
|
-
background: radial-gradient(circle at 50% 70%, rgba(
|
|
188
|
+
background: radial-gradient(circle at 50% 70%, rgba(250, 250, 250, 0.92), rgba(250, 250, 250, 0));
|
|
189
189
|
filter: blur(2px);
|
|
190
190
|
pointer-events: none;
|
|
191
191
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
.main-tab-btn {
|
|
7
7
|
flex: 1;
|
|
8
8
|
text-align: center;
|
|
9
|
-
border: 1px solid
|
|
10
|
-
background:
|
|
9
|
+
border: 1px solid var(--color-border);
|
|
10
|
+
background: var(--color-surface-tint);
|
|
11
11
|
border-radius: var(--radius-lg);
|
|
12
12
|
padding: 12px 14px;
|
|
13
13
|
cursor: pointer;
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
|
|
27
27
|
.main-tab-btn.active {
|
|
28
28
|
border-color: var(--color-brand);
|
|
29
|
-
box-shadow:
|
|
29
|
+
box-shadow: var(--shadow-card-hover);
|
|
30
30
|
color: var(--color-text-primary);
|
|
31
|
-
background: linear-gradient(135deg,
|
|
31
|
+
background: linear-gradient(135deg, var(--color-brand-light), var(--color-surface-tint));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.status-strip {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
.lang-toggle-btn {
|
|
54
54
|
border: 1px solid var(--color-border);
|
|
55
|
-
background:
|
|
55
|
+
background: var(--color-surface-tint);
|
|
56
56
|
border-radius: 10px;
|
|
57
57
|
padding: 10px 12px;
|
|
58
58
|
font-size: 12px;
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
.lang-toggle-btn:hover {
|
|
65
65
|
border-color: rgba(0, 0, 0, 0.22);
|
|
66
|
-
background:
|
|
66
|
+
background: var(--color-surface);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
.lang-toggle-btn.active {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
max-width: 100%;
|
|
78
78
|
padding: 6px 10px;
|
|
79
79
|
border: 1px solid var(--color-border);
|
|
80
|
-
background:
|
|
80
|
+
background: var(--color-surface-tint);
|
|
81
81
|
box-shadow: none;
|
|
82
82
|
display: inline-flex;
|
|
83
83
|
flex-direction: row;
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
padding: 10px 12px;
|
|
112
112
|
border-radius: 10px;
|
|
113
113
|
border: 1px solid var(--color-border);
|
|
114
|
-
background:
|
|
114
|
+
background: var(--color-surface-tint);
|
|
115
115
|
box-shadow: none;
|
|
116
116
|
display: grid;
|
|
117
117
|
gap: 6px;
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
z-index: 12;
|
|
184
184
|
margin: 0 -28px 14px;
|
|
185
185
|
padding: 0 28px 12px;
|
|
186
|
-
background: linear-gradient(180deg,
|
|
186
|
+
background: linear-gradient(180deg, var(--color-bg-topbar-strong) 0%, var(--color-bg-topbar-soft) 78%, var(--color-bg-topbar-clear) 100%);
|
|
187
187
|
backdrop-filter: blur(8px);
|
|
188
188
|
}
|
|
189
189
|
|
|
@@ -381,6 +381,23 @@
|
|
|
381
381
|
white-space: nowrap;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
.session-item-hot {
|
|
385
|
+
font-size: var(--font-size-caption);
|
|
386
|
+
padding: 2px 6px;
|
|
387
|
+
border-radius: 999px;
|
|
388
|
+
background: rgba(208, 88, 58, 0.14);
|
|
389
|
+
border: 1px solid rgba(208, 88, 58, 0.26);
|
|
390
|
+
color: rgba(140, 46, 24, 0.9);
|
|
391
|
+
line-height: 1;
|
|
392
|
+
white-space: nowrap;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.session-item-cwd {
|
|
396
|
+
flex: 1 1 160px;
|
|
397
|
+
min-width: 160px;
|
|
398
|
+
color: var(--color-text-muted);
|
|
399
|
+
}
|
|
400
|
+
|
|
384
401
|
.session-preview {
|
|
385
402
|
border: 1px solid var(--color-border-soft);
|
|
386
403
|
border-radius: var(--radius-xl);
|
|
@@ -37,6 +37,68 @@
|
|
|
37
37
|
border-top: 1px dashed var(--color-border-soft);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
.session-filter-chips {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-wrap: wrap;
|
|
43
|
+
gap: 8px;
|
|
44
|
+
margin-top: 4px;
|
|
45
|
+
margin-bottom: 2px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.session-filter-chip {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 6px;
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
padding: 6px 10px;
|
|
54
|
+
border-radius: 999px;
|
|
55
|
+
border: 1px solid var(--color-border-soft);
|
|
56
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.78) 100%);
|
|
57
|
+
color: var(--color-text-secondary);
|
|
58
|
+
font-size: var(--font-size-caption);
|
|
59
|
+
font-weight: var(--font-weight-secondary);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
62
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
|
|
63
|
+
min-width: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.session-filter-chip:hover {
|
|
67
|
+
border-color: var(--color-border-strong);
|
|
68
|
+
transform: translateY(-1px);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.session-filter-chip-title {
|
|
72
|
+
opacity: 0.75;
|
|
73
|
+
flex: 0 0 auto;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.session-filter-chip-value {
|
|
77
|
+
min-width: 0;
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
text-overflow: ellipsis;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
max-width: 520px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.session-filter-chip-close {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
width: 18px;
|
|
89
|
+
height: 18px;
|
|
90
|
+
border-radius: 999px;
|
|
91
|
+
border: 1px solid rgba(70, 86, 110, 0.22);
|
|
92
|
+
background: rgba(70, 86, 110, 0.08);
|
|
93
|
+
line-height: 1;
|
|
94
|
+
flex: 0 0 auto;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.session-filter-chip:focus-visible {
|
|
98
|
+
outline: 3px solid rgba(201, 94, 75, 0.25);
|
|
99
|
+
outline-offset: 2px;
|
|
100
|
+
}
|
|
101
|
+
|
|
40
102
|
.session-toolbar-footer .quick-option {
|
|
41
103
|
margin: 0;
|
|
42
104
|
padding: 6px 10px;
|