@mzzsfy/dsh-usage-dash 0.7.0 → 0.7.1

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 CHANGED
@@ -31,7 +31,7 @@
31
31
 
32
32
  - 模型匹配:`model` 为两段式 `vendor/model`(首个 `/` 分段,模型段允许含 `/`),两段各自可 `*` 通配;匹配链为 全名精确 > 模型名精确(`*/model`,跨供应商同模型名同价)> 供应商精确(`vendor/*`,同供应商多模型同价)> `*/*` 全通,档位相同按数组序取首个命中,高档条件不满足逐层落低档;全链无命中不计费用并计 unpriced。提交(编辑器保存与 pricing POST)强制两段式,单段旧形态(`*` 或裸名)不再合法
33
33
  - 同模型多规则(分时段定价):组内附加计费规则从上到下首个「条件全过」者生效,全不命中落组内默认价(无条件规则,恒兜底);附加规则卡带 ↑/↓ 调序。注意同一模型的多条规则请使用相同的模型键写法——不同写法(如 `*/model` 与 `vendor/model`)分属不同档位,按档位优先级而非数组序取胜
34
- - 条件类型(数组内 AND,空数组恒生效),**所有范围条件统一双侧包含**(含起始含结束,from===to 即单点/单日;相邻区间请写 1~15 与 16~31,端点重叠时数组靠前者优先),编辑器支持添加/编辑/删除:每条计费规则下方「+添加条件」按钮追加一条默认条件(时段默认全天 00:00~23:59,行内类型下拉切换即重置为该类型默认值:周几默认空、号段默认全月 1~31、日期段默认当天单日);编辑器端校验拒绝非法时刻(需 HH:MM)、周几(需 0-6)、月号(需 1-31)、非规范日期与日期段倒序(时段/号段倒序是跨午夜/跨月环绕,合法)
34
+ - 条件类型(数组内 AND,空数组恒生效),**所有范围条件统一双侧包含**(含起始含结束,from===to 即单点/单日;相邻区间请写 1~15 与 16~31,端点重叠时数组靠前者优先),编辑器支持添加/编辑/删除:每条计费规则下方「+添加条件」按钮追加一条默认条件(时段默认全天 00:00~23:59,行内类型下拉切换即重置为该类型默认值:周几默认空、号段默认全月 1~31、日期段默认当天单日);编辑器端校验拒绝非法时刻(需 24 小时制 HH:MM)、周几(需 0-6)、月号(需 1-31)、非规范日期与日期段倒序(时段/号段倒序是跨午夜/跨月环绕,合法);每日时段为自绘 HH:MM 文本输入(原生 time 控件的段位由浏览器按系统区域决定,部分环境渲染秒段导致带秒值被拒),全角数字折半角,凑满四位自动补冒号,粘贴带秒串自动截取前四位
35
35
  - `dailyWindow`:`{ from: 'HH:MM', to: 'HH:MM' }` 每日时段双侧包含,from<to 正向,from>to 跨午夜,from===to 单点;全天即 00:00~23:59
36
36
  - `weekdays`:`{ days: [0-6] }` 星期几(集合,非范围),0=周日,空数组不成立;编辑器为日~六七枚 pill 多选
37
37
  - `monthDays`:`{ from, to }` 月内号段双侧包含整数(1-31),from>to 跨月环绕(账单周期),from===to 单日(如 5~5 即 5 号);2 月无 31 号自然不触发
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mzzsfy/dsh-usage-dash",
3
3
  "description": "用量统计面板:天/小时/分钟三粒度 token 与请求统计,26 周活跃热力图,会话底栏信息接管与官方动作行回合费用芯片,多条件分时段定价规则与费用估算;复刻自 github.com/HaoyueQin/dsh-usage-statistics-panel,感谢原作者 HaoyueQin",
4
- "version": "0.7.0",
4
+ "version": "0.7.1",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
package/src/client.js CHANGED
@@ -289,7 +289,7 @@ const MESSAGES_ZH = {
289
289
  'weekday.4': '四',
290
290
  'weekday.5': '五',
291
291
  'weekday.6': '六',
292
- condTime: '需 HH:MM',
292
+ condTime: '需 24 小时制时刻 HH:MM(时:分,如 09:30)',
293
293
  condWeekday: '需 0-6 整数',
294
294
  condMonthDay: '需 1-31 整数',
295
295
  condDate: '需 YYYY-MM-DD',
@@ -432,7 +432,7 @@ const MESSAGES_EN = {
432
432
  'weekday.4': 'Th',
433
433
  'weekday.5': 'Fr',
434
434
  'weekday.6': 'Sa',
435
- condTime: 'Requires HH:MM',
435
+ condTime: 'Requires 24-hour HH:MM time (hours:minutes, e.g. 09:30)',
436
436
  condWeekday: 'Requires integer 0-6',
437
437
  condMonthDay: 'Requires integer 1-31',
438
438
  condDate: 'Requires YYYY-MM-DD',
@@ -1556,6 +1556,7 @@ function turnCostTitleText(t, tokenUsage) {
1556
1556
  // ===== 定价编辑器纯函数(校验/规整/默认值) =====
1557
1557
  const PRICE_KEYS = ['input', 'output', 'cacheRead', 'cacheWrite']
1558
1558
  const HHMM_PATTERN = /^\d{1,2}:\d{2}$/
1559
+ const HHMM_PLACEHOLDER = 'HH:MM'
1559
1560
  const ISO_DAY_PATTERN_CLIENT = /^\d{4}-\d{2}-\d{2}$/
1560
1561
  // 时刻分量界:小时/分钟均双闭
1561
1562
  const HOUR_MAX = 23
@@ -1565,6 +1566,8 @@ const WEEKDAY_MAX = 6
1565
1566
  const MONTH_DAY_MIN = 1
1566
1567
  const MONTH_DAY_MAX = 31
1567
1568
  const FULL_DAY_WINDOW = { from: '00:00', to: '23:59' }
1569
+ // HH:MM 输入框内联宽(size 属性字符数):占位符容量 + 光标余量,配合 .ud-cond-fields 的 width:auto
1570
+ const HHMM_INPUT_SIZE = HHMM_PLACEHOLDER.length + 2
1568
1571
  const CONDITION_KIND_OPTIONS = ['dailyWindow', 'weekdays', 'monthDays', 'dateRange']
1569
1572
  const CONDITION_KIND_LABEL_KEYS = {
1570
1573
  dailyWindow: 'condDailyWindow',
@@ -1580,6 +1583,27 @@ const parseHHMM = (value) => {
1580
1583
  return hours >= 0 && hours <= HOUR_MAX && minutes >= 0 && minutes <= MINUTE_MAX ? value : null
1581
1584
  }
1582
1585
 
1586
+ // HH:MM 输入净化:自绘文本框永不出现秒段。成型仅限两种形态——纯数字满四位补冒号(连续键入/纯数字秒串粘贴),
1587
+ // 带冒号三段时刻截取前两段(带秒串粘贴自愈);其余形态(中间插字/杂串)原样放行交保存校验显式报错,防静默重排出错误合法时刻
1588
+ const HHMM_INPUT_DIGITS = 4
1589
+ const HHMM_PURE_DIGITS_PATTERN = /^\d+$/
1590
+ const HHMM_SECONDS_TAIL_PATTERN = /:\d{2}$/
1591
+ const HHMM_THREE_SEGMENT_PATTERN = /^\d{1,2}:\d{2}:\d{2}$/
1592
+ const FULLWIDTH_DIGIT_PATTERN = /[0-9]/g
1593
+ const FULLWIDTH_COLON = ':'
1594
+ const FULLWIDTH_ZERO = '0'.codePointAt(0)
1595
+ const normalizeHHMMInput = (raw) => {
1596
+ if (typeof raw !== 'string') return ''
1597
+ const half = raw.replace(FULLWIDTH_DIGIT_PATTERN, (char) => String(char.codePointAt(0) - FULLWIDTH_ZERO)).replaceAll(FULLWIDTH_COLON, ':')
1598
+ if (HHMM_PURE_DIGITS_PATTERN.test(half) && half.length >= HHMM_INPUT_DIGITS) {
1599
+ return `${half.slice(0, 2)}:${half.slice(2, 4)}`
1600
+ }
1601
+ if (HHMM_THREE_SEGMENT_PATTERN.test(half)) {
1602
+ return half.replace(HHMM_SECONDS_TAIL_PATTERN, '')
1603
+ }
1604
+ return half
1605
+ }
1606
+
1583
1607
  const isValidMonthDay = (value) => Number.isInteger(value) && value >= MONTH_DAY_MIN && value <= MONTH_DAY_MAX
1584
1608
 
1585
1609
  const isValidWeekday = (value) => Number.isInteger(value) && value >= WEEKDAY_MIN && value <= WEEKDAY_MAX
@@ -2733,6 +2757,17 @@ body[data-ds-dark-theme] .ud-panel{--ud-chart-1:color-mix(in srgb,#0576ff 65%,wh
2733
2757
  onChange: (event) => patchCondition({ [field]: event.target.value }),
2734
2758
  }),
2735
2759
  errorTextOf(`${condPath}.${field}`))
2760
+ // 时段自绘 HH:MM 输入:原生 time 控件段位由浏览器按 locale 决定,部分环境渲染秒段,产出带秒值被校验拒绝
2761
+ const hhmmInput = (field, labelKey) => h('label', { key: field, className: 'ud-field' },
2762
+ h('span', { className: 'ud-field-label' }, t(labelKey)),
2763
+ h('input', {
2764
+ type: 'text', className: 'ud-input', inputMode: 'numeric', autoComplete: 'off',
2765
+ spellCheck: false, placeholder: HHMM_PLACEHOLDER,
2766
+ size: HHMM_INPUT_SIZE,
2767
+ value: condition[field] ?? '',
2768
+ onChange: (event) => patchCondition({ [field]: normalizeHHMMInput(event.target.value) }),
2769
+ }),
2770
+ errorTextOf(`${condPath}.${field}`))
2736
2771
  // 号段 from/to 同域 1~31(双闭),统一上限
2737
2772
  const numberInput = (field, labelKey) => h('label', { key: field, className: 'ud-field' },
2738
2773
  h('span', { className: 'ud-field-label' }, t(labelKey)),
@@ -2757,7 +2792,7 @@ body[data-ds-dark-theme] .ud-panel{--ud-chart-1:color-mix(in srgb,#0576ff 65%,wh
2757
2792
  }, t(`weekday.${day}`))
2758
2793
  }))
2759
2794
  const fields = {
2760
- dailyWindow: [textInput('from', 'time', 'condFrom'), textInput('to', 'time', 'condTo')],
2795
+ dailyWindow: [hhmmInput('from', 'condFrom'), hhmmInput('to', 'condTo')],
2761
2796
  weekdays: [weekdaysPills()],
2762
2797
  monthDays: [numberInput('from', 'condFrom'), numberInput('to', 'condTo')],
2763
2798
  dateRange: [textInput('from', 'date', 'condFrom'), textInput('to', 'date', 'condTo')],
@@ -91,6 +91,7 @@ const {
91
91
  resolvePointQuery,
92
92
  formatDateTimeInput,
93
93
  parseLocalDateTime,
94
+ normalizeHHMMInput,
94
95
  shortDay,
95
96
  smoothPath,
96
97
  tipPlace,
@@ -1065,6 +1066,40 @@ test('validatePricingRules 拒绝非法时刻与时刻字段缺失', () => {
1065
1066
  assert.equal(errors.has('4.conditions.0'), false)
1066
1067
  })
1067
1068
 
1069
+ test('normalizeHHMMInput 满四位纯数字补冒号成型', () => {
1070
+ // Given 值为纯数字串且位数达四位 When 净化 Then 末两位前插冒号;纯数字秒串(140000)粘贴同路径自愈
1071
+ assert.equal(normalizeHHMMInput('0830'), '08:30')
1072
+ assert.equal(normalizeHHMMInput('140000'), '14:00')
1073
+ assert.equal(normalizeHHMMInput('12345'), '12:34')
1074
+ })
1075
+
1076
+ test('normalizeHHMMInput 三段时刻截取前两段', () => {
1077
+ // Given 粘贴带秒串(HH:MM:SS)When 净化 Then 截取时:分两段自愈
1078
+ assert.equal(normalizeHHMMInput('14:00:00'), '14:00')
1079
+ assert.equal(normalizeHHMMInput('8:05:59'), '8:05')
1080
+ assert.equal(normalizeHHMMInput('14:00:0'), '14:00:0')
1081
+ })
1082
+
1083
+ test('normalizeHHMMInput 其余形态原样放行', () => {
1084
+ // Given 中间插字重排(08:530)或杂串或未满四位 When 净化 Then 不插手,交保存校验显式报错而非静默重排
1085
+ assert.equal(normalizeHHMMInput('08:530'), '08:530')
1086
+ assert.equal(normalizeHHMMInput('2026-03-15 14:00'), '2026-03-15 14:00')
1087
+ assert.equal(normalizeHHMMInput('083'), '083')
1088
+ assert.equal(normalizeHHMMInput('8:3'), '8:3')
1089
+ assert.equal(normalizeHHMMInput('14:00'), '14:00')
1090
+ assert.equal(normalizeHHMMInput(''), '')
1091
+ assert.equal(normalizeHHMMInput(null), '')
1092
+ assert.equal(normalizeHHMMInput(undefined), '')
1093
+ })
1094
+
1095
+ test('normalizeHHMMInput 全角数字折半角后走同规则', () => {
1096
+ // Given IME 全角数字输入 When 净化 Then 先折半角再走成型规则,输出恒为半角
1097
+ assert.equal(normalizeHHMMInput('14:00'), '14:00')
1098
+ assert.equal(normalizeHHMMInput('14:00:00'), '14:00')
1099
+ assert.equal(normalizeHHMMInput('1400'), '14:00')
1100
+ assert.equal(normalizeHHMMInput('083'), '083')
1101
+ })
1102
+
1068
1103
  test('validatePricingRules 接受 from===to 单点与单日', () => {
1069
1104
  // Given 双闭下时段/号段/日期段 from===to When 校验 Then 全部合法
1070
1105
  const rules = [