codexmate 0.1.15 → 0.1.17

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.
@@ -1000,6 +1000,12 @@ const vi = Object.freeze({
1000
1000
  'usage.summary.avgMessagesPerSession': 'TB tin nhắn/phiên',
1001
1001
  'usage.summary.busiestDay': 'Ngày bận nhất',
1002
1002
  'usage.summary.busiestHour': 'Giờ cao điểm',
1003
+ 'usage.kpi.tokens': 'Tổng token',
1004
+ 'usage.kpi.sessions': 'Số phiên',
1005
+ 'usage.kpi.messages': 'Số tin nhắn',
1006
+ 'usage.kpi.dailyAvg': 'Token trung bình/ngày',
1007
+ 'usage.kpi.busiestDay': 'Ngày bận nhất',
1008
+ 'usage.wave.summary': 'Tổng {total} · đỉnh {peakDay} ({peakValue})',
1003
1009
  'usage.currentSession.title': 'Phiên hiện tại',
1004
1010
  'usage.currentSession.apiDuration': 'Thời gian API',
1005
1011
  'usage.currentSession.totalDuration': 'Tổng thời gian',
@@ -1128,6 +1134,10 @@ const vi = Object.freeze({
1128
1134
  'status.openclawConfig': 'Cấu hình OpenClaw',
1129
1135
  'status.workspaceFile': 'File không gian làm việc',
1130
1136
  'status.configMode': 'Chế độ cấu hình',
1137
+ 'status.kilocodeProvider': 'Nhà cung cấp Kilocode',
1138
+ 'status.kilocodeModel': 'Mô hình Kilocode',
1139
+ 'status.piProvider': 'Nhà cung cấp Pi',
1140
+ 'status.piModel': 'Mô hình Pi',
1131
1141
  'status.currentProvider': 'Nhà cung cấp hiện tại',
1132
1142
  'status.currentModel': 'Model hiện tại',
1133
1143
  'status.quickSwitchProvider': 'Chuyển nhanh nhà cung cấp',
@@ -1319,6 +1329,14 @@ const vi = Object.freeze({
1319
1329
  'pi.providers.json.desc': 'Xem trước bản ghi cuối từ thay đổi biểu mẫu; các chỉnh sửa được gộp vào models.json khi lưu.',
1320
1330
  'pi.providers.json.previewTitle': 'Xem trước',
1321
1331
  'pi.providers.json.editTitle': 'Chỉnh sửa JSON',
1332
+ 'pi.files.title': 'JSON tập tin',
1333
+ 'pi.files.desc': 'Hiển thị đầy đủ và chỉnh sửa trực tiếp settings.json và models.json của Pi; kiểm tra tính hợp lệ của JSON trước khi lưu, nội dung không hợp lệ sẽ không được ghi.',
1334
+ 'pi.files.settingsTitle': 'settings.json (cài đặt đang dùng)',
1335
+ 'pi.files.modelsTitle': 'models.json (nhà cung cấp và mô hình)',
1336
+ 'pi.history.apply': 'Áp dụng bản ghi này',
1337
+ 'pi.history.applied': 'Đã áp dụng lịch sử',
1338
+ 'pi.history.applyFailed': 'Áp dụng lịch sử thất bại',
1339
+ 'pi.history.confirm': 'Áp dụng bản ghi lịch sử này vào tệp? Nội dung hiện tại sẽ được chụp nhanh trước.',
1322
1340
  'pi.providers.models.remove': 'Xóa',
1323
1341
  'pi.providers.models.search': 'Tìm kiếm mô hình khả dụng…',
1324
1342
  'pi.providers.models.remoteCount': 'Đã lấy {count} mô hình, bấm để thêm',
@@ -826,6 +826,12 @@ const zhTw = Object.freeze({
826
826
  'usage.summary.avgMessagesPerSession': '平均每會話消息',
827
827
  'usage.summary.busiestDay': '最忙日',
828
828
  'usage.summary.busiestHour': '高峰時段',
829
+ 'usage.kpi.tokens': '總 Token',
830
+ 'usage.kpi.sessions': '會話數',
831
+ 'usage.kpi.messages': '訊息數',
832
+ 'usage.kpi.dailyAvg': '日均 Token',
833
+ 'usage.kpi.busiestDay': '最忙日',
834
+ 'usage.wave.summary': '本期共 {total} · 峰值 {peakDay}({peakValue})',
829
835
  'usage.currentSession.title': '目前會話',
830
836
  'usage.currentSession.apiDuration': 'API時長',
831
837
  'usage.currentSession.totalDuration': '總時長',
@@ -1029,6 +1035,10 @@ const zhTw = Object.freeze({
1029
1035
  'status.opencodeConfig': 'OpenCode 設定',
1030
1036
  'status.workspaceFile': '工作區文件',
1031
1037
  'status.configMode': '設定模式',
1038
+ 'status.kilocodeProvider': 'Kilocode 供應商',
1039
+ 'status.kilocodeModel': 'Kilocode 模型',
1040
+ 'status.piProvider': 'Pi 供應商',
1041
+ 'status.piModel': 'Pi 模型',
1032
1042
  'status.currentProvider': '目前 Provider',
1033
1043
  'status.currentModel': '目前模型',
1034
1044
  'status.quickSwitchProvider': '快速切換提供商',
@@ -1348,6 +1358,14 @@ const zhTw = Object.freeze({
1348
1358
  'pi.providers.json.desc': '即時預覽表單異動合併後的最終紀錄;儲存時併入 models.json。',
1349
1359
  'pi.providers.json.previewTitle': '預覽',
1350
1360
  'pi.providers.json.editTitle': '編輯 JSON',
1361
+ 'pi.files.title': '檔案 JSON',
1362
+ 'pi.files.desc': '完整展示並直接編輯 Pi 的 settings.json 與 models.json;儲存前校驗 JSON 合法性,非法內容不會寫入。',
1363
+ 'pi.files.settingsTitle': 'settings.json(生效設定)',
1364
+ 'pi.files.modelsTitle': 'models.json(供應商與模型)',
1365
+ 'pi.history.apply': '套用此紀錄',
1366
+ 'pi.history.applied': '已套用歷史紀錄',
1367
+ 'pi.history.applyFailed': '套用歷史紀錄失敗',
1368
+ 'pi.history.confirm': '將此歷史紀錄套用到檔案?目前檔案內容會先被快照儲存。',
1351
1369
  'pi.providers.models.remove': '刪除',
1352
1370
  'pi.providers.models.search': '搜尋可用模型…',
1353
1371
  'pi.providers.models.remoteCount': '已取得 {count} 個可用模型,點擊新增',
@@ -826,6 +826,12 @@ const zh = Object.freeze({
826
826
  'usage.summary.avgMessagesPerSession': '平均每会话消息',
827
827
  'usage.summary.busiestDay': '最忙日',
828
828
  'usage.summary.busiestHour': '高峰时段',
829
+ 'usage.kpi.tokens': '总 Token',
830
+ 'usage.kpi.sessions': '会话数',
831
+ 'usage.kpi.messages': '消息数',
832
+ 'usage.kpi.dailyAvg': '日均 Token',
833
+ 'usage.kpi.busiestDay': '最忙日',
834
+ 'usage.wave.summary': '本期共 {total} · 峰值 {peakDay}({peakValue})',
829
835
  'usage.currentSession.title': '当前会话',
830
836
  'usage.currentSession.apiDuration': 'API时长',
831
837
  'usage.currentSession.totalDuration': '总时长',
@@ -1029,6 +1035,10 @@ const zh = Object.freeze({
1029
1035
  'status.opencodeConfig': 'OpenCode 配置',
1030
1036
  'status.workspaceFile': '工作区文件',
1031
1037
  'status.configMode': '配置模式',
1038
+ 'status.kilocodeProvider': 'Kilocode 提供商',
1039
+ 'status.kilocodeModel': 'Kilocode 模型',
1040
+ 'status.piProvider': 'Pi 提供商',
1041
+ 'status.piModel': 'Pi 模型',
1032
1042
  'status.currentProvider': '当前 Provider',
1033
1043
  'status.currentModel': '当前模型',
1034
1044
  'status.quickSwitchProvider': '快速切换提供商',
@@ -1348,6 +1358,14 @@ const zh = Object.freeze({
1348
1358
  'pi.providers.json.desc': '实时预览表单改动合并后的最终记录;保存时并入 models.json。',
1349
1359
  'pi.providers.json.previewTitle': '预览',
1350
1360
  'pi.providers.json.editTitle': '编辑 JSON',
1361
+ 'pi.files.title': '文件 JSON',
1362
+ 'pi.files.desc': '完整展示并直接编辑 Pi 的 settings.json 与 models.json;保存前校验 JSON 合法性,非法内容不会写入。',
1363
+ 'pi.files.settingsTitle': 'settings.json(生效设置)',
1364
+ 'pi.files.modelsTitle': 'models.json(供应商与模型)',
1365
+ 'pi.history.apply': '应用此记录',
1366
+ 'pi.history.applied': '已应用历史记录',
1367
+ 'pi.history.applyFailed': '应用历史记录失败',
1368
+ 'pi.history.confirm': '将此历史记录应用到文件?当前文件内容会先被快照保存。',
1351
1369
  'pi.providers.models.remove': '删除',
1352
1370
  'pi.providers.models.search': '搜索可用模型…',
1353
1371
  'pi.providers.models.remoteCount': '已拉取 {count} 个可用模型,点击添加',
@@ -1,10 +1,14 @@
1
1
  export function createSkillsComputed() {
2
2
  return {
3
3
  skillsTargetLabel() {
4
- return this.skillsTargetApp === 'claude' ? 'Claude Code' : 'Codex';
4
+ if (this.skillsTargetApp === 'claude') return 'Claude Code';
5
+ if (this.skillsTargetApp === 'pi') return 'Pi';
6
+ return 'Codex';
5
7
  },
6
8
  skillsDefaultRootPath() {
7
- return this.skillsTargetApp === 'claude' ? '~/.claude/skills' : '~/.codex/skills';
9
+ if (this.skillsTargetApp === 'claude') return '~/.claude/skills';
10
+ if (this.skillsTargetApp === 'pi') return '~/.pi/agent/skills';
11
+ return '~/.codex/skills';
8
12
  },
9
13
  filteredSkillsList() {
10
14
  const list = Array.isArray(this.skillsList) ? this.skillsList : [];
@@ -12,7 +12,7 @@ export function createSkillsMethods({ api }) {
12
12
  if (app == null) {
13
13
  return 'codex';
14
14
  }
15
- if (app === 'codex' || app === 'claude') {
15
+ if (app === 'codex' || app === 'claude' || app === 'pi') {
16
16
  return app;
17
17
  }
18
18
  throw createUnsupportedSkillsTargetAppError(app);
@@ -471,6 +471,26 @@
471
471
  <span class="value">{{ opencodeConfigPath || t('common.notSelected') }}</span>
472
472
  </div>
473
473
  </template>
474
+ <template v-else-if="configMode === 'kilocode'">
475
+ <div class="status-chip">
476
+ <span class="label">{{ t('status.kilocodeProvider') }}</span>
477
+ <span class="value">{{ kilocodeProvider || t('common.notSelected') }}</span>
478
+ </div>
479
+ <div class="status-chip">
480
+ <span class="label">{{ t('status.kilocodeModel') }}</span>
481
+ <span class="value">{{ kilocodeModel || t('common.notSelected') }}</span>
482
+ </div>
483
+ </template>
484
+ <template v-else-if="configMode === 'pi'">
485
+ <div class="status-chip">
486
+ <span class="label">{{ t('status.piProvider') }}</span>
487
+ <span class="value">{{ piActiveProvider || t('common.notSelected') }}</span>
488
+ </div>
489
+ <div class="status-chip">
490
+ <span class="label">{{ t('status.piModel') }}</span>
491
+ <span class="value">{{ piActiveModel || t('common.notSelected') }}</span>
492
+ </div>
493
+ </template>
474
494
  <template v-else>
475
495
  <div class="status-chip">
476
496
  <span class="label">{{ t('status.configMode') }}</span>
@@ -23,6 +23,14 @@
23
23
  @click="setSkillsTargetApp('claude', { silent: false })">
24
24
  Claude Code
25
25
  </button>
26
+ <button
27
+ type="button"
28
+ :class="['market-target-chip', { active: skillsTargetApp === 'pi' }]"
29
+ :aria-pressed="skillsTargetApp === 'pi'"
30
+ :disabled="loading || !!initError || skillsMarketBusy"
31
+ @click="setSkillsTargetApp('pi', { silent: false })">
32
+ Pi
33
+ </button>
26
34
  </div>
27
35
  <button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
28
36
  {{ skillsLoading ? t('common.refreshing') : t('common.refresh') }}
@@ -178,6 +178,82 @@
178
178
  </div>
179
179
  </div>
180
180
  </section>
181
+ <section class="selector-section">
182
+ <div class="selector-header">
183
+ <span class="selector-title">{{ t('pi.files.title') }}</span>
184
+ </div>
185
+ <div class="config-template-hint">{{ t('pi.files.desc') }}</div>
186
+ <label class="form-label">{{ t('pi.files.settingsTitle') }}</label>
187
+ <textarea
188
+ class="template-textarea config-json-editor"
189
+ v-model="piSettingsJsonDraft"
190
+ spellcheck="false"
191
+ autocomplete="off"
192
+ :disabled="!isToolConfigWriteAllowed('pi')"
193
+ @input="piSettingsJsonInput"
194
+ placeholder="{}"></textarea>
195
+ <p v-if="piSettingsJsonError" class="config-template-hint error-text">{{ piSettingsJsonError }}</p>
196
+ <div class="settings-tab-actions">
197
+ <button type="button" class="btn-tool" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
198
+ <button type="button" class="btn-mini" @click="openPiConfigHistory('settings')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
199
+ </div>
200
+ <details v-if="piHistoryTarget === 'settings'" class="prompt-history-panel" open>
201
+ <summary class="prompt-history-summary">
202
+ <span>{{ t('common.history') }}</span>
203
+ <button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
204
+ </summary>
205
+ <div class="prompt-history-body">
206
+ <ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
207
+ <li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
208
+ <span class="prompt-history-time">{{ item.id }}</span>
209
+ <span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
210
+ </li>
211
+ </ul>
212
+ <div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
213
+ <div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
214
+ <div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
215
+ <div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
216
+ <pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
217
+ <button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
218
+ </div>
219
+ </div>
220
+ </details>
221
+ <label class="form-label">{{ t('pi.files.modelsTitle') }}</label>
222
+ <textarea
223
+ class="template-textarea config-json-editor"
224
+ v-model="piModelsJsonDraft"
225
+ spellcheck="false"
226
+ autocomplete="off"
227
+ :disabled="!isToolConfigWriteAllowed('pi')"
228
+ @input="piModelsJsonInput"
229
+ placeholder="{}"></textarea>
230
+ <p v-if="piModelsJsonError" class="config-template-hint error-text">{{ piModelsJsonError }}</p>
231
+ <div class="settings-tab-actions">
232
+ <button type="button" class="btn-tool" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
233
+ <button type="button" class="btn-mini" @click="openPiConfigHistory('models')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
234
+ </div>
235
+ <details v-if="piHistoryTarget === 'models'" class="prompt-history-panel" open>
236
+ <summary class="prompt-history-summary">
237
+ <span>{{ t('common.history') }}</span>
238
+ <button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
239
+ </summary>
240
+ <div class="prompt-history-body">
241
+ <ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
242
+ <li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
243
+ <span class="prompt-history-time">{{ item.id }}</span>
244
+ <span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
245
+ </li>
246
+ </ul>
247
+ <div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
248
+ <div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
249
+ <div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
250
+ <div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
251
+ <pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
252
+ <button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
253
+ </div>
254
+ </div>
255
+ </details>
256
+ </section>
181
257
  <div v-if="!isToolConfigWriteAllowed('pi')" class="tool-config-write-overlay">
182
258
  <div class="tool-config-write-overlay-card">
183
259
  <div class="tool-config-write-overlay-title">{{ t('toolConfig.pi.lockedTitle') }}</div>
@@ -11,14 +11,14 @@
11
11
 
12
12
  <div class="docs-toolbar-grid">
13
13
  <div class="docs-toolbar-card">
14
- <label class="form-label" for="docs-install-package-manager">{{ t('common.packageManager') }}</label>
15
- <select id="docs-install-package-manager" class="form-input" v-model="installPackageManager">
16
- <option value="npm">npm</option>
17
- <option value="pnpm">pnpm</option>
18
- <option value="bun">bun</option>
19
- </select>
14
+ <label class="form-label">{{ t('common.packageManager') }}</label>
15
+ <div class="install-action-tabs">
16
+ <button type="button" class="btn-mini" :class="{ active: installPackageManager === 'npm' }" @click="installPackageManager = 'npm'">npm</button>
17
+ <button type="button" class="btn-mini" :class="{ active: installPackageManager === 'pnpm' }" @click="installPackageManager = 'pnpm'">pnpm</button>
18
+ <button type="button" class="btn-mini" :class="{ active: installPackageManager === 'bun' }" @click="installPackageManager = 'bun'">bun</button>
19
+ </div>
20
20
  </div>
21
- <div class="docs-toolbar-card docs-toolbar-card-wide">
21
+ <div class="docs-toolbar-card">
22
22
  <label class="form-label">{{ t('common.mirror') }}</label>
23
23
  <div class="install-action-tabs">
24
24
  <button type="button" class="btn-mini" :class="{ active: installRegistryPreset === 'npmmirror' }" @click="setInstallRegistryPreset('npmmirror')">npmmirror</button>
@@ -29,7 +29,7 @@
29
29
  <div class="form-hint install-registry-hint" v-if="installRegistryPreview">{{ t('docs.registryHintPrefix') }} --registry={{ installRegistryPreview }}</div>
30
30
  <div class="form-hint install-registry-hint" v-else-if="installRegistryPreset === 'custom'">{{ t('docs.registryHintCustom') }}</div>
31
31
  </div>
32
- <div class="docs-toolbar-card docs-toolbar-card-wide">
32
+ <div class="docs-toolbar-card">
33
33
  <label class="form-label">{{ t('common.action') }}</label>
34
34
  <div class="install-action-tabs">
35
35
  <button type="button" class="btn-mini" :class="{ active: installCommandAction === 'install' }" @click="setInstallCommandAction('install')">{{ t('common.install') }}</button>
@@ -39,20 +39,6 @@
39
39
  </div>
40
40
  </div>
41
41
 
42
- <div class="docs-summary-strip">
43
- <div class="docs-summary-item">
44
- <span class="docs-summary-label">{{ t('common.targets') }}</span>
45
- <strong class="docs-summary-value">{{ installTargetCards.length }}</strong>
46
- </div>
47
- <div class="docs-summary-item">
48
- <span class="docs-summary-label">{{ t('common.currentPm') }}</span>
49
- <strong class="docs-summary-value">{{ String(installPackageManager || 'npm').toUpperCase() }}</strong>
50
- </div>
51
- <div class="docs-summary-item docs-summary-item-wide">
52
- <span class="docs-summary-label">{{ t('common.registry') }}</span>
53
- <strong class="docs-summary-value">{{ installRegistryPreview || 'npmmirror' }}</strong>
54
- </div>
55
- </div>
56
42
  </div>
57
43
 
58
44
  <div class="selector-section">
@@ -25,6 +25,14 @@
25
25
  @click="setSkillsTargetApp('claude', { silent: false })">
26
26
  Claude Code
27
27
  </button>
28
+ <button
29
+ type="button"
30
+ :class="['skills-target-chip', { active: skillsTargetApp === 'pi' }]"
31
+ :aria-pressed="skillsTargetApp === 'pi'"
32
+ :disabled="loading || !!initError || skillsMarketBusy"
33
+ @click="setSkillsTargetApp('pi', { silent: false })">
34
+ Pi
35
+ </button>
28
36
  </div>
29
37
  </div>
30
38
  <div class="skills-header-actions">
@@ -4,8 +4,8 @@
4
4
  id="panel-plugins"
5
5
  role="tabpanel"
6
6
  aria-labelledby="tab-plugins">
7
- <div class="plugins-layout">
8
- <aside class="plugins-sidebar" :aria-label="t('plugins.sidebar.ariaList')">
7
+ <div :class="['plugins-layout', { 'plugins-layout-single': !pluginsSidebarVisible }]">
8
+ <aside v-if="pluginsSidebarVisible" class="plugins-sidebar" :aria-label="t('plugins.sidebar.ariaList')">
9
9
  <div class="selector-header plugins-sidebar-header">
10
10
  <div>
11
11
  <span class="selector-title">{{ t('plugins.sidebar.title') }}</span>
@@ -44,8 +44,15 @@
44
44
  </div>
45
45
  <div v-else-if="pluginsActiveId === 'prompt-templates'" class="plugins-panel">
46
46
  <div class="plugins-panel-head">
47
- <div class="plugins-panel-title">{{ t('plugins.promptTemplates.title') }}</div>
48
- <div v-if="pluginsActiveAttribution" class="plugins-panel-note">{{ pluginsActiveAttribution }}</div>
47
+ <div>
48
+ <div class="plugins-panel-title">{{ t('plugins.promptTemplates.title') }}</div>
49
+ <div v-if="pluginsActiveAttribution" class="plugins-panel-note">{{ pluginsActiveAttribution }}</div>
50
+ </div>
51
+ <div v-if="!pluginsSidebarVisible" class="settings-tab-actions">
52
+ <button type="button" class="btn-tool btn-tool-compact" @click="loadPluginsOverview({ forceRefresh: true, silent: false })" :disabled="loading || !!initError || pluginsLoading">
53
+ {{ pluginsLoading ? t('plugins.refreshing') : t('plugins.refresh') }}
54
+ </button>
55
+ </div>
49
56
  </div>
50
57
 
51
58
  <div class="prompt-templates-modebar" role="tablist" :aria-label="t('plugins.promptTemplates.mode.aria')">
@@ -65,14 +72,7 @@
65
72
 
66
73
  <div v-if="promptTemplatesMode === 'compose'" class="prompt-compose">
67
74
  <div class="prompt-compose-workspace">
68
- <div class="prompt-compose-selected">
69
- <div class="prompt-compose-selected-title">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.name) ? promptComposerActiveTemplate.name : t('plugins.promptTemplates.compose.chooseTemplate') }}</div>
70
- <div class="prompt-compose-selected-meta">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.description) ? promptComposerActiveTemplate.description : t('plugins.promptTemplates.compose.chooseTemplateHint') }}</div>
71
- <div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
72
- {{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy || '', maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}
73
- </div>
74
- </div>
75
-
75
+ <div class="prompt-compose-main">
76
76
  <div class="prompt-compose-form">
77
77
  <label class="form-label">{{ t('plugins.promptTemplates.compose.selectTemplate') }}</label>
78
78
  <select
@@ -86,7 +86,7 @@
86
86
  </select>
87
87
 
88
88
  <div v-if="!promptComposerActiveTemplate" class="plugins-panel-note">{{ t('plugins.promptTemplates.compose.empty') }}</div>
89
- <div v-else-if="!promptComposerActiveTemplate.isBuiltin" class="plugins-panel-note">{{ t('plugins.promptTemplates.compose.varsHint') }}</div>
89
+ <div v-else-if="promptComposerActiveTemplate.description" class="plugins-panel-note">{{ promptComposerActiveTemplate.description }}</div>
90
90
  <div v-if="promptComposerActiveTemplate && !promptComposerActiveTemplate.isBuiltin" class="prompt-compose-actions">
91
91
  <button
92
92
  type="button"
@@ -121,6 +121,7 @@
121
121
  </div>
122
122
  </div>
123
123
 
124
+ </div>
124
125
  <div class="prompt-preview-block prompt-compose-preview">
125
126
  <div class="prompt-vars-head">
126
127
  <div>
@@ -50,30 +50,29 @@
50
50
  <span class="usage-spinner" aria-hidden="true"></span>
51
51
  </div>
52
52
 
53
- <!-- Hero 区域:合并当前会话条 + 主要指标 -->
54
- <div class="usage-hero">
55
- <div v-if="usageCurrentSessionStats" class="usage-hero-active">
56
- <span class="usage-hero-active-dot"></span>
57
- <span class="usage-hero-active-label">{{ usageCurrentSessionStats.label }}</span>
58
- <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.tokenLabel }} tokens</span>
59
- <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.apiDurationLabel }} API</span>
60
- <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.totalDurationLabel }} total</span>
61
- </div>
53
+ <div v-if="usageCurrentSessionStats" class="usage-active-strip" role="status">
54
+ <span class="usage-hero-active-dot"></span>
55
+ <span class="usage-hero-active-label">{{ usageCurrentSessionStats.label }}</span>
56
+ <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.tokenLabel }} tokens</span>
57
+ <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.apiDurationLabel }} API</span>
58
+ <span class="usage-hero-active-stat">{{ usageCurrentSessionStats.totalDurationLabel }} total</span>
59
+ </div>
62
60
 
63
- <div class="usage-hero-metrics">
64
- <div class="usage-hero-main">{{ usageHeroMainValue }}</div>
65
- <div class="usage-hero-sub">
66
- <span>{{ usageHeroSubLabel }}</span>
67
- <span v-if="usageHeroDelta" :class="['usage-hero-delta', usageHeroDeltaClass]">
68
- {{ usageHeroDelta }}
69
- </span>
70
- </div>
61
+ <!-- KPI 指标卡片行 -->
62
+ <div class="usage-kpi-grid">
63
+ <div v-for="card in usageKpiCards" :key="card.key" class="usage-kpi-card">
64
+ <div class="usage-kpi-label">{{ card.label }}</div>
65
+ <div class="usage-kpi-value" :title="card.title || card.value">{{ card.value }}</div>
66
+ <span v-if="card.delta" :class="['usage-hero-delta', card.deltaClass]">{{ card.delta }}</span>
71
67
  </div>
72
68
  </div>
73
69
 
74
70
  <!-- 波浪图 -->
75
71
  <section v-if="sessionUsageWave.points && sessionUsageWave.points.length" class="usage-wave-section">
76
- <div class="usage-card-title">{{ t('usage.daily.title') }}</div>
72
+ <div class="usage-card-title usage-wave-title">
73
+ <span>{{ t('usage.daily.title') }}</span>
74
+ <span v-if="usageWaveHeaderSummary" class="usage-wave-summary">{{ usageWaveHeaderSummary }}</span>
75
+ </div>
77
76
  <div class="usage-wave-container">
78
77
  <div class="usage-wave-yaxis">
79
78
  <span v-for="tick in sessionUsageWave.yTicks" :key="tick.value"
@@ -150,13 +149,14 @@
150
149
  <!-- Top Sessions -->
151
150
  <section class="usage-card">
152
151
  <div class="usage-card-title">{{ t('usage.sessions.topDensity') }}</div>
153
- <div v-if="!sessionUsageCharts.topSessionsByMessages.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
152
+ <div v-if="!usageRankedLists.topSessions.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
154
153
  <div v-else class="usage-list-compact">
155
- <div v-for="item in sessionUsageCharts.topSessionsByMessages" :key="item.key + '-dense'" class="usage-list-compact-item" @click="selectSession(item)" :title="item.title">
154
+ <div v-for="item in usageRankedLists.topSessions" :key="item.key + '-dense'" class="usage-list-compact-item" @click="selectSession(item)" :title="item.title">
156
155
  <span class="usage-list-bullet">·</span>
157
156
  <div class="usage-list-compact-content">
158
157
  <div class="usage-list-title">{{ item.title }}</div>
159
158
  <div class="usage-list-meta">{{ item.messageCount }} msgs · {{ item.sourceLabel }} · {{ item.updatedAtLabel }}</div>
159
+ <div class="usage-list-bar" aria-hidden="true"><span class="usage-list-bar-fill" :style="{ width: item.barPercent + '%' }"></span></div>
160
160
  </div>
161
161
  </div>
162
162
  </div>
@@ -165,13 +165,14 @@
165
165
  <!-- Recent Activity -->
166
166
  <section class="usage-card">
167
167
  <div class="usage-card-title">{{ t('usage.recent.title') }}</div>
168
- <div v-if="!sessionUsageCharts.recentSessions.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
168
+ <div v-if="!usageRankedLists.recentSessions.length" class="usage-list-value">{{ t('usage.sessions.empty') }}</div>
169
169
  <div v-else class="usage-list-compact">
170
- <div v-for="item in sessionUsageCharts.recentSessions" :key="item.key" class="usage-list-compact-item" @click="selectSession(item)">
170
+ <div v-for="item in usageRankedLists.recentSessions" :key="item.key" class="usage-list-compact-item" @click="selectSession(item)">
171
171
  <span class="usage-list-bullet">·</span>
172
172
  <div class="usage-list-compact-content">
173
173
  <div class="usage-list-title">{{ item.title }}</div>
174
174
  <div class="usage-list-meta">{{ item.messageCount }} msgs · {{ item.sourceLabel }} · {{ item.updatedAtLabel }}</div>
175
+ <div class="usage-list-bar" aria-hidden="true"><span class="usage-list-bar-fill" :style="{ width: item.barPercent + '%' }"></span></div>
175
176
  </div>
176
177
  </div>
177
178
  </div>
@@ -180,14 +181,15 @@
180
181
  <!-- Top Paths -->
181
182
  <section class="usage-paths-section">
182
183
  <div class="usage-card-title">{{ t('usage.paths.title') }}</div>
183
- <div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
184
+ <div v-if="!usageRankedLists.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
184
185
  <div v-else class="usage-list-paths">
185
- <div v-for="(item, index) in sessionUsageCharts.topPaths" :key="item.path" class="usage-list-path-row">
186
+ <div v-for="(item, index) in usageRankedLists.topPaths" :key="item.path" class="usage-list-path-row">
186
187
  <span class="usage-list-path-rank">{{ index + 1 }}</span>
187
188
  <div class="usage-list-path-content">
188
189
  <div class="usage-list-path" :title="item.path">{{ item.path }}</div>
189
- <div class="usage-list-path-stat">{{ item.count }}</div>
190
+ <div class="usage-list-bar" aria-hidden="true"><span class="usage-list-bar-fill" :style="{ width: item.barPercent + '%' }"></span></div>
190
191
  </div>
192
+ <div class="usage-list-path-stat">{{ item.count }}</div>
191
193
  </div>
192
194
  </div>
193
195
  </section>