@gehennawu/dsh-service 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -38,7 +38,7 @@ The plugin also appears under **Plugins → Plugin configuration**, with six hos
38
38
 
39
39
  - 7-day stacked bar chart of input/output/cache tokens with blue/orange/teal legend
40
40
  - Filter by project; hover for exact values
41
- - Model breakdown as horizontal stacked bars (same legend colors), sorted by total tokens, largest first; each row keeps `x times · Cache hit x% · Input xM token · Output xM token`
41
+ - Model breakdown as horizontal stacked bars (same legend colors) with a "Today / Last 7 days / All time" toggle on the right of the list header (default: last 7 days): Today aggregates only the current day, Last 7 days ranks within the chart's window, and All time covers every date in the index (bounded by session retention); each row keeps `x times · Cache hit x% · Input xM token · Output xM token`
42
42
  - Steps whose provider reports no token usage are excluded from the statistics
43
43
  - Last-24-hour model/tool error statistics, collapsed by default
44
44
 
package/README.md CHANGED
@@ -38,7 +38,7 @@
38
38
 
39
39
  - 近 7 天输入/输出/缓存 token 堆叠柱图,蓝/橙/青图例
40
40
  - 按项目筛选,鼠标悬停显示精确数值
41
- - 模型明细为横向堆叠柱形图(沿用主图图例配色),按 token 总量从多到少排序;每行附「x次 · 缓存命中 x% · 输入 xM token · 输出 xM token」明细
41
+ - 模型明细为横向堆叠柱形图(沿用主图图例配色),列表头部右侧提供「今日 / 近 7 天 / 累计」切换标签(默认近 7 天):今日只聚合当天,近 7 天按主图同窗口排序,累计覆盖索引内全部日期(受会话持久化留存范围限制);每行附「x次 · 缓存命中 x% · 输入 xM token · 输出 xM token」明细
42
42
  - 提供方未上报 token 用量的模型步骤不纳入统计
43
43
  - 最近 24 小时模型/工具报错统计,默认折叠
44
44
 
package/client.js CHANGED
@@ -313,8 +313,15 @@ window.__ModuleLoader__.load({
313
313
  'usage.axis': 'token 纵轴',
314
314
  'usage.models.more': '展开其余 {count} 个模型',
315
315
  'usage.models.less': '收起模型列表',
316
- 'usage.modelSortHint': '按 token 总量从多到少排列',
317
- 'usage.modelBar': '{model}:共 {total} token',
316
+ 'usage.modelScope.today': '今日',
317
+ 'usage.modelScope.week': ' 7 ',
318
+ 'usage.modelScope.all': '累计',
319
+ 'usage.modelSortHint.today': '按今日 token 从多到少排列',
320
+ 'usage.modelSortHint.week': '按近 7 天 token 从多到少排列',
321
+ 'usage.modelSortHint.all': '按累计 token 从多到少排列',
322
+ 'usage.modelBar.today': '{model}:今日 {total} token',
323
+ 'usage.modelBar.week': '{model}:近 7 天 {total} token',
324
+ 'usage.modelBar.all': '{model}:累计 {total} token',
318
325
  'usage.refresh': '刷新统计',
319
326
  'usage.refreshing': '刷新中…',
320
327
  'usage.empty': '尚未建立使用统计索引。点击刷新统计开始只读建立索引。',
@@ -727,8 +734,15 @@ window.__ModuleLoader__.load({
727
734
  'usage.axis': 'token vertical axis',
728
735
  'usage.models.more': 'Show {count} more models',
729
736
  'usage.models.less': 'Collapse model list',
730
- 'usage.modelSortHint': 'Sorted by total tokens, largest first',
731
- 'usage.modelBar': '{model}: {total} tokens in total',
737
+ 'usage.modelScope.today': 'Today',
738
+ 'usage.modelScope.week': 'Last 7 days',
739
+ 'usage.modelScope.all': 'All time',
740
+ 'usage.modelSortHint.today': 'Sorted by tokens used today, largest first',
741
+ 'usage.modelSortHint.week': 'Sorted by tokens in the last 7 days, largest first',
742
+ 'usage.modelSortHint.all': 'Sorted by all-time tokens, largest first',
743
+ 'usage.modelBar.today': '{model}: {total} tokens today',
744
+ 'usage.modelBar.week': '{model}: {total} tokens in the last 7 days',
745
+ 'usage.modelBar.all': '{model}: {total} tokens in total',
732
746
  'usage.refresh': 'Refresh usage',
733
747
  'usage.refreshing': 'Refreshing…',
734
748
  'usage.empty': 'No usage index yet. Select Refresh usage to build the read-only index.',
@@ -2820,6 +2834,8 @@ window.__ModuleLoader__.load({
2820
2834
  const [modelErrorsOpen, setModelErrorsOpen] = useState(false)
2821
2835
  const [toolErrorsOpen, setToolErrorsOpen] = useState(false)
2822
2836
  const [modelsOpen, setModelsOpen] = useState(false)
2837
+ // 模型列表口径:today=仅今日 / week=近 7 天(默认,保持既有视图)/ all=宿主索引内全部日期累计。
2838
+ const [modelScope, setModelScope] = useState('week')
2823
2839
  const [activeTab, setActiveTab] = useState('overview')
2824
2840
  // 版本详情行内展开(不用浮层:弹层会被设置模态盖住)
2825
2841
  const [channelOpen, setChannelOpen] = useState(false)
@@ -3152,6 +3168,8 @@ window.__ModuleLoader__.load({
3152
3168
  const tabPanel = { padding: '14px 2px 2px', color: 'var(--dsw-alias-label-primary)' }
3153
3169
  const inlineTab = { background: 'transparent', color: 'var(--dsw-alias-label-secondary)', border: 0, borderBottom: '2px solid transparent', padding: '8px 10px', cursor: 'pointer', fontSize: '13px', fontWeight: 550, transition: 'color 120ms, border-color 120ms' }
3154
3170
  const inlineTabActive = { color: 'var(--dsw-alias-brand-primary)', borderBottom: '2px solid var(--dsw-alias-brand-primary)', fontWeight: 700 }
3171
+ // 模型列表头部的紧凑口径切换:沿用下划线标签语言,但按 11px 行高缩比。
3172
+ const compactTab = Object.assign({}, inlineTab, { padding: '3px 6px', fontSize: '11px' })
3155
3173
  const sectionTitle = { fontSize: '14px', fontWeight: 700, margin: '0 0 8px', color: 'var(--dsw-alias-label-primary)' }
3156
3174
 
3157
3175
  const formatSize = (bytes) => {
@@ -3242,23 +3260,34 @@ window.__ModuleLoader__.load({
3242
3260
  const selectedProjects = usageProject === 'all'
3243
3261
  ? (usage?.projects || []).map((project) => project.id)
3244
3262
  : [usageProject]
3245
- const modelTotals = new Map()
3246
- for (const day of usageDays) {
3247
- const source = usage?.days?.[day.key]
3248
- if (!source) continue
3263
+ const emptyModelTotals = (id) => ({ id, steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 })
3264
+ const accumulateModelTotals = (buckets, model) => {
3265
+ const existing = buckets.get(model.id) || emptyModelTotals(model.id)
3266
+ existing.steps += model.totals.steps || 0
3267
+ existing.inputTokens += model.totals.inputTokens || 0
3268
+ existing.outputTokens += model.totals.outputTokens || 0
3269
+ existing.cacheReadTokens += model.totals.cacheReadTokens || 0
3270
+ existing.cacheWriteTokens += model.totals.cacheWriteTokens || 0
3271
+ buckets.set(model.id, existing)
3272
+ }
3273
+ const modelTodayTotals = new Map()
3274
+ const modelWeekTotals = new Map()
3275
+ const modelAllTotals = new Map()
3276
+ const weekDayKeys = new Set(usageDays.map((day) => day.key))
3277
+ const todayDayKey = usageDays[usageDays.length - 1].key
3278
+ // 累计口径遍历宿主下发的全部日期键(可早于 7 天窗口);周/今日按窗口命中累积。
3279
+ for (const [dayKey, source] of Object.entries(usage?.days || {})) {
3280
+ const targets = [modelAllTotals]
3281
+ if (weekDayKeys.has(dayKey)) targets.push(modelWeekTotals)
3282
+ if (dayKey === todayDayKey) targets.push(modelTodayTotals)
3249
3283
  for (const project of source.projects) {
3250
3284
  if (!selectedProjects.includes(project.id)) continue
3251
3285
  for (const model of project.models) {
3252
- const existing = modelTotals.get(model.id) || { id: model.id, steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }
3253
- existing.steps += model.totals.steps || 0
3254
- existing.inputTokens += model.totals.inputTokens || 0
3255
- existing.outputTokens += model.totals.outputTokens || 0
3256
- existing.cacheReadTokens += model.totals.cacheReadTokens || 0
3257
- existing.cacheWriteTokens += model.totals.cacheWriteTokens || 0
3258
- modelTotals.set(model.id, existing)
3286
+ for (const buckets of targets) accumulateModelTotals(buckets, model)
3259
3287
  }
3260
3288
  }
3261
3289
  }
3290
+ const scopedModelTotals = modelScope === 'today' ? modelTodayTotals : modelScope === 'all' ? modelAllTotals : modelWeekTotals
3262
3291
  const diagnosticDetail = (check) => {
3263
3292
  const detail = String(check.detail ?? '')
3264
3293
  if (check.id === 'session-storage' && check.status === 'ok') return translate('health.detail.session-storage.ok', { count: detail })
@@ -3297,7 +3326,7 @@ window.__ModuleLoader__.load({
3297
3326
  React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, translate(label)),
3298
3327
  React.createElement('span', { style: { fontWeight: 650 } }, value)))
3299
3328
  )
3300
- const sortedModels = [...modelTotals.values()].sort((a, b) => modelTotalTokens(b) - modelTotalTokens(a) || b.steps - a.steps || a.id.localeCompare(b.id))
3329
+ const sortedModels = [...scopedModelTotals.values()].sort((a, b) => modelTotalTokens(b) - modelTotalTokens(a) || b.steps - a.steps || a.id.localeCompare(b.id))
3301
3330
  const visibleModels = modelsOpen ? sortedModels : sortedModels.slice(0, 3)
3302
3331
  const hiddenModelCount = Math.max(0, sortedModels.length - 3)
3303
3332
  const maxModelTokens = Math.max(1, ...visibleModels.map((model) => modelTotalTokens(model)))
@@ -3360,7 +3389,15 @@ window.__ModuleLoader__.load({
3360
3389
  summaryBlock('today', 'usage.today', todayTotals),
3361
3390
  summaryBlock('seven', 'usage.sevenDays', sevenTotals)),
3362
3391
  React.createElement('div', { 'data-testid': 'usage-model-list', style: { marginTop: '10px', padding: '8px 10px', borderRadius: '8px', background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l1)' } },
3363
- React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', marginBottom: '4px' } }, translate('usage.modelSortHint')),
3392
+ React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px', flexWrap: 'wrap', marginBottom: '4px' } },
3393
+ React.createElement('div', { 'data-testid': 'usage-model-sort-hint', style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } }, translate(`usage.modelSortHint.${modelScope}`)),
3394
+ React.createElement('div', { 'data-testid': 'usage-model-scope-tabs', style: { display: 'flex', gap: '2px' } },
3395
+ ['today', 'week', 'all'].map((scopeId) => React.createElement('button', {
3396
+ key: scopeId,
3397
+ 'data-testid': `usage-model-scope-${scopeId}`,
3398
+ style: Object.assign({}, compactTab, modelScope === scopeId ? inlineTabActive : { color: 'var(--dsw-alias-label-secondary)', borderBottom: '2px solid transparent' }),
3399
+ onClick: () => setModelScope(scopeId),
3400
+ }, translate(`usage.modelScope.${scopeId}`))))),
3364
3401
  visibleModels.map((model, index) => {
3365
3402
  const total = modelTotalTokens(model)
3366
3403
  const fillWidth = `${Math.round(total / maxModelTokens * 10000) / 100}%`
@@ -3368,7 +3405,7 @@ window.__ModuleLoader__.load({
3368
3405
  React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: '12px', alignItems: 'baseline', fontSize: '12px' } },
3369
3406
  React.createElement('span', { style: { overflowWrap: 'anywhere' } }, model.id),
3370
3407
  React.createElement('span', { style: { fontWeight: 650, whiteSpace: 'nowrap' } }, formatTokenValue(total))),
3371
- React.createElement('div', { 'data-testid': `usage-model-bar-${model.id}`, 'data-value': total, 'aria-label': translate('usage.modelBar', { model: model.id, total: formatTokenValue(total) }), style: { height: '8px', borderRadius: '4px', marginTop: '6px', overflow: 'hidden', background: 'var(--dsw-alias-border-l1)' } },
3408
+ React.createElement('div', { 'data-testid': `usage-model-bar-${model.id}`, 'data-value': total, 'aria-label': translate(`usage.modelBar.${modelScope}`, { model: model.id, total: formatTokenValue(total) }), style: { height: '8px', borderRadius: '4px', marginTop: '6px', overflow: 'hidden', background: 'var(--dsw-alias-border-l1)' } },
3372
3409
  React.createElement('div', { style: { display: 'flex', height: '100%', width: fillWidth } },
3373
3410
  usageSegments.map(([metricName, , color]) => {
3374
3411
  const value = modelSegmentValue(model, metricName)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gehennawu/dsh-service",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "DSH Web 自托管运维面板:安全重启、健康监控、备份和 Linux 权限维护。",
5
5
  "type": "module",
6
6
  "scripts": {