@kenz1117/dsh-ui-usage-billing 0.9.20 → 1.0.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/lib/index.js CHANGED
@@ -324,6 +324,67 @@ const MODEL_CATALOG = [
324
324
  output: 16
325
325
  }
326
326
  },
327
+ {
328
+ key: "glm-4.5-air",
329
+ name: "GLM-4.5-Air",
330
+ provider: "智谱 AI",
331
+ colorVar: "dsw-static-blue-300",
332
+ price: {
333
+ currency: "CNY",
334
+ input: .8,
335
+ cacheHit: .16,
336
+ output: 2
337
+ }
338
+ },
339
+ {
340
+ key: "glm-4.7",
341
+ name: "GLM-4.7",
342
+ provider: "智谱 AI",
343
+ colorVar: "dsw-static-blue-400",
344
+ price: {
345
+ currency: "CNY",
346
+ input: 4,
347
+ cacheHit: 1,
348
+ output: 16
349
+ },
350
+ estimated: true
351
+ },
352
+ {
353
+ key: "glm-5-turbo",
354
+ name: "GLM-5-Turbo",
355
+ provider: "智谱 AI",
356
+ colorVar: "dsw-static-blue-500",
357
+ price: {
358
+ currency: "CNY",
359
+ input: 5,
360
+ cacheHit: 1.2,
361
+ output: 22
362
+ }
363
+ },
364
+ {
365
+ key: "glm-5.1",
366
+ name: "GLM-5.1",
367
+ provider: "智谱 AI",
368
+ colorVar: "dsw-static-blue-600",
369
+ price: {
370
+ currency: "CNY",
371
+ input: 6,
372
+ cacheHit: 1.2,
373
+ output: 24
374
+ }
375
+ },
376
+ {
377
+ key: "glm-5v-turbo",
378
+ name: "GLM-5V-Turbo",
379
+ provider: "智谱 AI",
380
+ colorVar: "dsw-static-blue-300",
381
+ price: {
382
+ currency: "CNY",
383
+ input: 5,
384
+ cacheHit: 1.2,
385
+ output: 22
386
+ }
387
+ },
327
388
  {
328
389
  key: "qwen-3.8-max",
329
390
  name: "Qwen3.8 Max",
@@ -492,6 +553,30 @@ const MODEL_CATALOG = [
492
553
  output: 8.4
493
554
  }
494
555
  },
556
+ {
557
+ key: "minimax-m2.7",
558
+ name: "MiniMax-M2.7",
559
+ provider: "MiniMax",
560
+ colorVar: "dsw-static-amber-400",
561
+ price: {
562
+ currency: "CNY",
563
+ input: 2.1,
564
+ cacheHit: .42,
565
+ output: 8.4
566
+ }
567
+ },
568
+ {
569
+ key: "minimax-m2.7-highspeed",
570
+ name: "MiniMax-M2.7-highspeed",
571
+ provider: "MiniMax",
572
+ colorVar: "dsw-static-amber-500",
573
+ price: {
574
+ currency: "CNY",
575
+ input: 4.2,
576
+ cacheHit: .42,
577
+ output: 16.8
578
+ }
579
+ },
495
580
  {
496
581
  key: "ernie",
497
582
  name: "ERNIE-5.1",
@@ -735,6 +820,13 @@ const MODEL_KEY_ALIASES = {
735
820
  "deepseek-v4-flash-vision-exp": "flash-vision-exp",
736
821
  "deepseek-v4-pro": "pro",
737
822
  "glm-5.2": "glm",
823
+ "glm-4.5-air": "glm-4.5-air",
824
+ "glm-4.5air": "glm-4.5-air",
825
+ "glm-4.7": "glm-4.7",
826
+ "glm-5-turbo": "glm-5-turbo",
827
+ "glm-5.1": "glm-5.1",
828
+ "glm-5v-turbo": "glm-5v-turbo",
829
+ "glm-5v.1": "glm-5v-turbo",
738
830
  "qwen3.8-max": "qwen-3.8-max",
739
831
  "qwen3.7-max": "qwen-max",
740
832
  "qwen-max": "qwen-max",
@@ -745,7 +837,13 @@ const MODEL_KEY_ALIASES = {
745
837
  "kimi-k3": "kimi-k3",
746
838
  "minimax-m1": "minimax",
747
839
  "minimax-m2": "minimax",
748
- "minimax-m3": "minimax"
840
+ "minimax-m3": "minimax",
841
+ "minimax-m2.7": "minimax-m2.7",
842
+ "minimax-m2.7-highspeed": "minimax-m2.7-highspeed",
843
+ "minimax-m2.7-high-speed": "minimax-m2.7-highspeed",
844
+ "minimax-m2-7": "minimax-m2.7",
845
+ "minimax-m2-7-highspeed": "minimax-m2.7-highspeed",
846
+ "minimax-m2-7-high-speed": "minimax-m2.7-highspeed"
749
847
  };
750
848
  /**
751
849
  * 模型 id 归一化:小写、去括号附注(如 `gpt5.6 luna(go)` 只看主体)、再去所有
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 峰谷切换提醒浮层:切档前状态条(右下角或居中)。用 `position: fixed` 即可在
3
- * 任意宿主容器内覆盖整个视口,因此不需要 portal。布局为「档位徽标 + 大号等宽
4
- * 倒计时 + 一句说明 + 关闭」,克制冷调、无重力阴影。渲染是受控的:父组件把命中
3
+ * 任意宿主容器内覆盖整个视口,因此不需要 portal。布局按设计 peak-card:头部
4
+ * (档位 tag + 关闭)→ 大号等宽倒计时 → 一句说明。渲染是受控的:父组件把命中
5
5
  * (hit)与偏好传入,显示剩余分钟并在切换后消失。
6
6
  */
7
7
  import type { PeakAlertConfig, PeakAlertHit } from './peak-alert.ts';
@@ -1,7 +1,8 @@
1
1
  /**
2
- * PluginInfoCard: 「设置 → 插件信息」卡——展示头像/作者、GitHub 仓库、版本号、
3
- * 许可证等插件元信息。版本号来自服务端 usage-stats 的 `pluginVersion`(读自
4
- * 包 package.json),其余元信息静态来自 `plugin-info.ts`。无版本号时显示 em dash。
2
+ * PluginInfoCard: 「设置 → 插件信息」卡——图标 + 插件名/版本 tag + 描述,下接
3
+ * 元信息网格(作者 / 仓库 / npm / 许可证)。版本号来自服务端 usage-stats 的
4
+ * `pluginVersion`(读自包 package.json),其余元信息静态来自 `plugin-info.ts`。
5
+ * 无版本号时显示 em dash。
5
6
  */
6
7
  import type { UsageBillingKey } from './locales.ts';
7
8
  /** 信息卡 props:locale 函数 + 版本号(服务端下发,可为空)。 */
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.sessions' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.pricingTip' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.anomaly' | 'billing.workspaces' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
2
+ export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
3
3
  export declare const NS = "usageBilling";
4
4
  export declare const zh: Record<UsageBillingKey, string>;
5
5
  export declare const en: Record<UsageBillingKey, string>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kenz1117/dsh-ui-usage-billing",
3
3
  "description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
4
- "version": "0.9.20",
4
+ "version": "1.0.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -1,39 +0,0 @@
1
- /**
2
- * 供应商计费计划知识库(对标 dsh-spend 的 PROVIDER_KNOWLEDGE / PROVIDER_ALIASES)。
3
- *
4
- * 每个 provider 归入两种计费口径之一:
5
- * - `code` —— 订阅制(coding / token / agent plan):费用 = 订阅月费,额度按周期
6
- * 计量,token 用量不计费(不按 token 扣钱)。
7
- * - `token` —— 按量制:费用 = token × 单价(由 models.dev / OpenRouter / 内置目录
8
- * 定价)。无知识库命中的 provider 默认按 token 口径。
9
- *
10
- * `aliasOf` 把部署里杂乱的 provider id(glm / kimi / dashscope…)归一化到知识库键,
11
- * `planOf` 据此返回计费计划;日志出现的 provider 经此自动识别,无需人工配置。
12
- */
13
- /** 计费口径:code = 订阅制(计月费),token = 按量制(计 token 单价)。 */
14
- export type BillingPlanKind = 'code' | 'token';
15
- /** 订阅月费的币种(只支持单一币种计费,与仪表盘币种一致)。 */
16
- export type BillingCurrency = 'CNY' | 'USD';
17
- /** 一个 provider 的计费计划。 */
18
- export interface BillingPlan {
19
- /** 计费口径。 */
20
- kind: BillingPlanKind;
21
- /** 厂商显示名(未命中知识库时用 provider id 原样)。 */
22
- label: string;
23
- /** code 计划的订阅月费(单位 {@link currency});token 计划无此项。 */
24
- subscriptionMonthly?: number;
25
- /** 订阅月费币种。 */
26
- currency?: BillingCurrency;
27
- /** 订阅额度周期(天);额度按此周期计量。 */
28
- periodDays?: number;
29
- }
30
- /** 归一化 provider id 到知识库键;未命中的原样返回(避免误吞)。 */
31
- export declare function aliasOf(provider: string): string;
32
- /**
33
- * 某 provider 的计费计划:命中的 code 计划返回订阅口径,`PROVIDER_LABELS` 命中的
34
- * 按量厂商返回 token 口径(无月费);完全未知返回 undefined(调用方按 token 兜底)。
35
- */
36
- export declare function planOf(provider: string): BillingPlan | undefined;
37
- /** 是否已知为订阅制(code)provider。 */
38
- export declare function isCodePlan(provider: string): boolean;
39
- //# sourceMappingURL=plans.d.ts.map