@qilin-agent/client-ui-usage 0.0.0-stage → 3.1.5
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/LICENSE +21 -0
- package/README.i18n.yaml +37 -0
- package/README.md +70 -2
- package/README.zh.md +72 -0
- package/lib/client.js +660 -0
- package/lib/index.js +6 -0
- package/lib/types/client/UsageSection.d.ts +10 -0
- package/lib/types/client/chart.d.ts +143 -0
- package/lib/types/client/index.d.ts +17 -0
- package/lib/types/client/locales.d.ts +41 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +65 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-usage/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: cd39454ce914041d
|
|
7
|
+
zh: a0d485737f99c17b
|
|
8
|
+
/qilin-agent-client-ui-usage:
|
|
9
|
+
en: cdc33a09599e489b
|
|
10
|
+
zh: 2acc60cf6fdbdada
|
|
11
|
+
/qilin-agent-client-ui-usage/summary:
|
|
12
|
+
en: 92aabb575be0adb5
|
|
13
|
+
zh: 54ff20ca5d41ad4d
|
|
14
|
+
/qilin-agent-client-ui-usage/table-of-contents:
|
|
15
|
+
en: 9ef876754311ed9e
|
|
16
|
+
zh: f192f0880dceb717
|
|
17
|
+
/qilin-agent-client-ui-usage/use-this-page:
|
|
18
|
+
en: d98a5879d6b22575
|
|
19
|
+
zh: 31a24838994600ca
|
|
20
|
+
/qilin-agent-client-ui-usage/understand-the-implementation:
|
|
21
|
+
en: 714fc29bb2906705
|
|
22
|
+
zh: 2b9b6db76137358e
|
|
23
|
+
/qilin-agent-client-ui-usage/further-exploration:
|
|
24
|
+
en: 40054a3c65e28684
|
|
25
|
+
zh: 237154e1c28810f8
|
|
26
|
+
/qilin-agent-client-ui-usage/model-experience:
|
|
27
|
+
en: 858dd23111a6657c
|
|
28
|
+
zh: 2cd19dcd2b8b3875
|
|
29
|
+
/qilin-agent-client-ui-usage/model-experience/kv-cache-effect:
|
|
30
|
+
en: 5033978e4a6981be
|
|
31
|
+
zh: 4f15327f95c173a6
|
|
32
|
+
/qilin-agent-client-ui-usage/known-limitations-and-deferred-work:
|
|
33
|
+
en: b2bdd5b10487cd7b
|
|
34
|
+
zh: 77ac531248a0069a
|
|
35
|
+
/qilin-agent-client-ui-usage/known-limitations-and-deferred-work/dev-note:
|
|
36
|
+
en: 1cfccee090069017
|
|
37
|
+
zh: afb65ab9cf131a4e
|
package/README.md
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Per-account token usage in Settings: daily stacked bars with a foldable legend, a cache-hit-rate area, and a per-session table."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/client-ui-usage
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
The Usage settings section answers what one account spent. It reads that account's usage from its own runtime and draws the current month as one stacked bar per day — uncached input, output, and cache reads — with the share of prompt tokens served from cache drawn as an area behind the bars. Each legend entry folds its series out of the chart, hovering or focusing a bar reads back that day, and a table breaks the month down by session. All-time figures come from the ledger's own totals.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this page](#use-this-page)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-page"></a>
|
|
26
|
+
## Use this page
|
|
27
|
+
|
|
28
|
+
Open Settings and choose **Usage**. The page needs no configuration; it reads `GET /api/usage/me` from the runtime that serves the browser, which forwards the request to the deployment's ledger with the credential that runtime reports under.
|
|
29
|
+
|
|
30
|
+
| Part | What it shows |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Cards | Today, this month, all time, and all-time Turns and sessions, each with the cache hit rate of the traffic it covers |
|
|
33
|
+
| Chart | One stacked bar per day of the current month, with the hit-rate area behind them |
|
|
34
|
+
| Legend | One entry per bar series plus the area; selecting an entry folds that series out of the chart |
|
|
35
|
+
| Table | This month by session: input, output, cache reads, hit rate, and total |
|
|
36
|
+
|
|
37
|
+
An administrator's page reads the deployment instead of one account: the same chart summed over every account, and the table breaking the month down by account rather than by session.
|
|
38
|
+
|
|
39
|
+
The chart is keyboard-reachable: each bar takes focus, left and right move between days, and Escape dismisses the read-back. An unreachable deployment says so rather than drawing an empty month, and a ledger that held more Turns than one answer carries says the newest are shown.
|
|
40
|
+
|
|
41
|
+
<a id="understand-the-implementation"></a>
|
|
42
|
+
## Understand the implementation
|
|
43
|
+
|
|
44
|
+
`src/client/chart.ts` holds the page's arithmetic as pure functions: bucketing records into browser-local days (quiet days included, because a chart that closed a gap would draw a quiet week as a busy one), the cache hit rate, the fold state, and the layout of stacked bands, gridlines, and the area path. `UsageSection.tsx` holds the state a reader drives and renders those values; it draws no chart library and no literal colors.
|
|
45
|
+
|
|
46
|
+
<a id="further-exploration"></a>
|
|
47
|
+
## Further Exploration
|
|
48
|
+
|
|
49
|
+
- [Usage report](../../session/usage-report/README.md) is the runtime half: it reports Turns to the ledger and serves the read this page uses.
|
|
50
|
+
- [Settings general](../ui-settings-general/README.md) owns the settings shell this section registers into.
|
|
51
|
+
- [Web styling](../../../docs/web-styling.md) states the token and localization rules this page follows.
|
|
52
|
+
|
|
53
|
+
<a id="model-experience"></a>
|
|
54
|
+
## Model Experience
|
|
55
|
+
|
|
56
|
+
None, as this page reads a ledger and draws it, adding no prompt, tool, or model-visible output.
|
|
57
|
+
|
|
58
|
+
#### KV Cache effect
|
|
59
|
+
|
|
60
|
+
No request is altered; the page only reports the cache traffic the provider already billed.
|
|
61
|
+
|
|
62
|
+
## Known Limitations and Deferred Work
|
|
63
|
+
|
|
64
|
+
- **The month is the browser's month.** Days bucket by the browser's local calendar, so a reader who travels sees the month their own clock describes rather than the deployment's.
|
|
65
|
+
- **One answer is bounded.** The read carries the newest records a response bound allows; a month that reported more shows its newest days and says so.
|
|
66
|
+
- **Only an administrator sees other accounts.** Everyone else's page reads its own account alone, because the deployment's read is scoped by the credential the runtime reports under; an ordinary account cannot ask for another's usage.
|
|
67
|
+
|
|
68
|
+
<a id="dev-note"></a>
|
|
69
|
+
### Dev Note
|
|
70
|
+
|
|
71
|
+
The section registers into `settings.section` rather than adding a settings shell of its own, and its dictionaries ship with the package so the page has no locale of its own to maintain.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "设置里的按账户 token 用量:可按图例折叠的每日堆叠柱、缓存命中率面积图,以及按会话的明细表。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/client-ui-usage
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
设置里的「用量」一节回答用户关于自己账户的一个问题:花了多少。它从用户自己的运行时读回用量,把当月画成每天一根堆叠柱——未命中输入、输出、缓存读——并在柱后画出提示 token 中由缓存提供的比例。每个系列都能从图例里折叠掉,单根柱在悬停或获得焦点时读回当天的数字,下方表格把同一个月按会话拆开。全部时间的数据来自账本自身的合计。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本页](#use-this-page)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与待办](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-page"></a>
|
|
26
|
+
## 使用本页
|
|
27
|
+
|
|
28
|
+
打开设置,选择**用量**。本页不需要配置;它从服务浏览器的运行时读取 `GET /api/usage/me`,运行时会带上自己上报所用的凭据把请求转给部署的账本。
|
|
29
|
+
|
|
30
|
+
| 部分 | 展示内容 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| 指标卡 | 今天、本月、全部时间,以及全部时间的轮次数与会话数,各带它所覆盖流量的缓存命中率 |
|
|
33
|
+
| 图表 | 当月每天一根堆叠柱,柱后是命中率面积 |
|
|
34
|
+
| 图例 | 每个柱系列一项,另加面积一项;点选某项即把该系列折叠出图 |
|
|
35
|
+
| 表格 | 当月按会话:输入、输出、缓存读、命中率与合计 |
|
|
36
|
+
|
|
37
|
+
管理员的页面读到的是部署而不是单个账户:同一张图按全部账户求和,表格把当月按账户(而不是按会话)拆开。
|
|
38
|
+
|
|
39
|
+
图表可用键盘到达:每根柱可获得焦点,左右方向键在日期间移动,Escape 关闭读回。部署不可达时页面直说,而不是画一个空月份;当账本里的轮次超过单次应答容量时,页面说明只显示了最近的。
|
|
40
|
+
|
|
41
|
+
<a id="understand-the-implementation"></a>
|
|
42
|
+
## 理解实现
|
|
43
|
+
|
|
44
|
+
`src/client/chart.ts` 用纯函数承载本页的算术:把记录按浏览器本地日分桶(包含没有用量的日子,因为把空缺填平的图会把安静的一周画成繁忙的一周)、缓存命中率、折叠状态,以及堆叠段、网格线与面积路径的布局。`UsageSection.tsx` 承载读者驱动的状态并渲染这些值;它不引入任何图表库,也不写任何字面色。
|
|
45
|
+
|
|
46
|
+
<a id="further-exploration"></a>
|
|
47
|
+
## 进一步探索
|
|
48
|
+
|
|
49
|
+
- [Usage report](../../session/usage-report/README.zh.md) 是运行时那一半:它把轮次上报给账本,并提供本页所用的读取。
|
|
50
|
+
- [Settings general](../ui-settings-general/README.zh.md) 拥有本页注册进去的设置外壳。
|
|
51
|
+
- [Web styling](../../../docs/web-styling.zh.md) 说明本页遵循的令牌与本地化规则。
|
|
52
|
+
|
|
53
|
+
<a id="model-experience"></a>
|
|
54
|
+
## 模型体验
|
|
55
|
+
|
|
56
|
+
无:本页只读取账本并绘制它,不增加任何提示词、工具或模型可见输出。
|
|
57
|
+
|
|
58
|
+
#### KV 缓存影响
|
|
59
|
+
|
|
60
|
+
请求不被改动;本页只是把提供方已经计费的缓存流量报告出来。
|
|
61
|
+
|
|
62
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
63
|
+
## 已知限制与待办
|
|
64
|
+
|
|
65
|
+
- **月份是浏览器的月份。** 日期按浏览器本地日历分桶,因此外出的读者看到的是自己时钟描述的月份,而不是部署的。
|
|
66
|
+
- **单次应答有上界。** 读取只携带应答容量允许的最新记录;上报更多的月份会显示最新的日子并说明这一点。
|
|
67
|
+
- **只有管理员能看到其他账户。** 其他人的页面只读自己的账户:部署的读取由运行时上报所用的凭据限定作用域,普通账户无法索取他人的用量。
|
|
68
|
+
|
|
69
|
+
<a id="dev-note"></a>
|
|
70
|
+
### 开发备注
|
|
71
|
+
|
|
72
|
+
本节注册进 `settings.section`,而不是自建设置外壳;字典随包发布,因此本页没有自己需要维护的本地化层。
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-usage",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
//#region lib/types/client/locales.js
|
|
10
|
+
/** Copy dictionaries for the usage page. */
|
|
11
|
+
/** Dictionary namespace owned by this plugin. */
|
|
12
|
+
const NS = "usage";
|
|
13
|
+
/** English strings (the key-set source of truth for this pair). */
|
|
14
|
+
const en = {
|
|
15
|
+
"nav": "Usage",
|
|
16
|
+
"title": "Token usage",
|
|
17
|
+
"subtitle": "What this account spent, by day and by session. For account {account}.",
|
|
18
|
+
"scope.deployment": "Deployment-wide, every account",
|
|
19
|
+
"table.account": "Account",
|
|
20
|
+
"metric.input": "Uncached input",
|
|
21
|
+
"metric.output": "Output",
|
|
22
|
+
"metric.cacheRead": "Cache reads",
|
|
23
|
+
"metric.cacheWrite": "Cache writes",
|
|
24
|
+
"metric.total": "Total",
|
|
25
|
+
"metric.hitRate": "Cache hit rate",
|
|
26
|
+
"metric.sessions": "Sessions",
|
|
27
|
+
"metric.turns": "Turns",
|
|
28
|
+
"today": "Today",
|
|
29
|
+
"month": "This month",
|
|
30
|
+
"all": "All time",
|
|
31
|
+
"chart.title": "Token usage by day this month",
|
|
32
|
+
"chart.caption": "Bars stack the tokens spent each day; the area behind them is the share of prompt tokens served from cache.",
|
|
33
|
+
"chart.empty": "No usage has been reported yet.",
|
|
34
|
+
"chart.fold": "Fold {series}",
|
|
35
|
+
"chart.unfold": "Unfold {series}",
|
|
36
|
+
"table.title": "By session",
|
|
37
|
+
"table.session": "Session",
|
|
38
|
+
"table.input": "Input",
|
|
39
|
+
"table.output": "Output",
|
|
40
|
+
"table.cacheRead": "Cache read",
|
|
41
|
+
"table.hitRate": "Hit rate",
|
|
42
|
+
"table.total": "Total",
|
|
43
|
+
"table.sort": "Sort by this column",
|
|
44
|
+
"loading": "Reading this account’s usage…",
|
|
45
|
+
"unavailable": "The deployment could not be reached, so this page has nothing to show.",
|
|
46
|
+
"truncated": "Only the most recent records are shown."
|
|
47
|
+
};
|
|
48
|
+
/** Chinese strings for the same key set. */
|
|
49
|
+
const zh = {
|
|
50
|
+
"nav": "用量",
|
|
51
|
+
"title": "Token 用量",
|
|
52
|
+
"subtitle": "本账户的消耗,按天与按会话。账户:{account}。",
|
|
53
|
+
"scope.deployment": "部署范围:全部账户",
|
|
54
|
+
"table.account": "账户",
|
|
55
|
+
"metric.input": "未命中输入",
|
|
56
|
+
"metric.output": "输出",
|
|
57
|
+
"metric.cacheRead": "缓存读",
|
|
58
|
+
"metric.cacheWrite": "缓存写",
|
|
59
|
+
"metric.total": "合计",
|
|
60
|
+
"metric.hitRate": "缓存命中率",
|
|
61
|
+
"metric.sessions": "会话数",
|
|
62
|
+
"metric.turns": "轮次数",
|
|
63
|
+
"today": "今天",
|
|
64
|
+
"month": "本月",
|
|
65
|
+
"all": "全部时间",
|
|
66
|
+
"chart.title": "本月每日 token 用量",
|
|
67
|
+
"chart.caption": "柱子按天堆叠当日消耗;柱后的面积是提示 token 中由缓存提供的比例。",
|
|
68
|
+
"chart.empty": "还没有任何用量上报。",
|
|
69
|
+
"chart.fold": "折叠 {series}",
|
|
70
|
+
"chart.unfold": "展开 {series}",
|
|
71
|
+
"table.title": "按会话",
|
|
72
|
+
"table.session": "会话",
|
|
73
|
+
"table.input": "输入",
|
|
74
|
+
"table.output": "输出",
|
|
75
|
+
"table.cacheRead": "缓存读",
|
|
76
|
+
"table.hitRate": "命中率",
|
|
77
|
+
"table.total": "合计",
|
|
78
|
+
"table.sort": "按此列排序",
|
|
79
|
+
"loading": "正在读取本账户用量……",
|
|
80
|
+
"unavailable": "无法联系部署,页面暂无内容可显示。",
|
|
81
|
+
"truncated": "仅显示最近的记录。"
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-usage/src/client/UsageSection.module.css.mjs
|
|
85
|
+
const css = ".KgUhKW_page{color:var(--qilin-alias-label-primary);flex-direction:column;gap:18px;padding:4px 2px 24px;display:flex}.KgUhKW_title{margin:0 0 4px;font-size:1.05rem}.KgUhKW_subtitle{color:var(--qilin-alias-label-secondary);margin:0;font-size:.85rem}.KgUhKW_cards{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;display:grid}.KgUhKW_card{border:.5px solid var(--qilin-alias-border-l3);border-radius:var(--qilin-radius-md);background:var(--qilin-alias-bg-layer-2);padding:10px 12px}.KgUhKW_cardLabel{color:var(--qilin-alias-label-secondary);font-size:.75rem}.KgUhKW_cardValue{font-variant-numeric:tabular-nums;font-size:1.15rem}.KgUhKW_cardNote{color:var(--qilin-alias-label-tertiary);font-size:.7rem}.KgUhKW_chart{border:.5px solid var(--qilin-alias-border-l3);border-radius:var(--qilin-radius-md);background:var(--qilin-alias-bg-layer-2);padding:12px 12px 6px;position:relative}.KgUhKW_chartTitle{margin:0 0 2px;font-size:.9rem}.KgUhKW_caption{color:var(--qilin-alias-label-secondary);margin:0 0 8px;font-size:.75rem}.KgUhKW_plotRow{gap:6px;display:flex}.KgUhKW_tickColumn{width:34px;height:220px;position:relative}.KgUhKW_tickLabel{color:var(--qilin-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:10px;position:absolute;right:0;transform:translateY(-50%)}.KgUhKW_plotArea{flex:1;min-width:0;position:relative}.KgUhKW_plot{width:100%;height:220px;display:block;overflow:visible}.KgUhKW_grid{stroke:var(--qilin-alias-border-l2);stroke-width:.25px}.KgUhKW_tickLabel{fill:var(--qilin-alias-label-tertiary);font-size:3px}.KgUhKW_band{transition:opacity .12s}.KgUhKW_bar:focus-visible{outline:none}.KgUhKW_bar:focus-visible .KgUhKW_band{opacity:.7}.KgUhKW_area{fill:var(--qilin-alias-brand-primary);opacity:.35}.KgUhKW_legend{flex-wrap:wrap;gap:6px;margin-top:8px;display:flex}.KgUhKW_legendButton{border:.5px solid var(--qilin-alias-border-l3);corner-shape:round;color:var(--qilin-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;align-items:center;gap:6px;padding:3px 10px;font-size:.72rem;display:inline-flex}.KgUhKW_legendButton[aria-pressed=false]{color:var(--qilin-alias-label-tertiary);text-decoration:line-through}.KgUhKW_swatch{border-radius:2px;width:9px;height:9px}.KgUhKW_tooltip{border:.5px solid var(--qilin-alias-border-l3);border-radius:var(--qilin-radius-sm);background:var(--qilin-alias-bg-layer-3);font-variant-numeric:tabular-nums;pointer-events:none;white-space:nowrap;padding:6px 8px;font-size:.72rem;position:absolute;top:0;transform:translate(-50%)}.KgUhKW_tableWrap{border:.5px solid var(--qilin-alias-border-l3);border-radius:var(--qilin-radius-md);background:var(--qilin-alias-bg-layer-2);--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);overflow:auto}.KgUhKW_table{border-collapse:collapse;width:100%;font-size:.78rem}.KgUhKW_table th,.KgUhKW_table td{text-align:right;border-bottom:.5px solid var(--qilin-alias-border-l2);font-variant-numeric:tabular-nums;padding:6px 10px}.KgUhKW_table th:first-child,.KgUhKW_table td:first-child{text-align:left}.KgUhKW_table thead th{color:var(--qilin-alias-label-secondary);background:var(--qilin-alias-bg-layer-2);font-weight:500;position:sticky;top:0}.KgUhKW_state{color:var(--qilin-alias-label-secondary);font-size:.85rem}";
|
|
86
|
+
const tagId = "@qilin-agent/client-ui-usage/UsageSection.module.css";
|
|
87
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
88
|
+
const tag = document.createElement("style");
|
|
89
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-usage";
|
|
90
|
+
tag.dataset.pluginCss = tagId;
|
|
91
|
+
tag.textContent = css;
|
|
92
|
+
document.head.appendChild(tag);
|
|
93
|
+
}
|
|
94
|
+
var UsageSection_module_css_default = {
|
|
95
|
+
"area": "KgUhKW_area",
|
|
96
|
+
"band": "KgUhKW_band",
|
|
97
|
+
"bar": "KgUhKW_bar",
|
|
98
|
+
"caption": "KgUhKW_caption",
|
|
99
|
+
"card": "KgUhKW_card",
|
|
100
|
+
"cardLabel": "KgUhKW_cardLabel",
|
|
101
|
+
"cardNote": "KgUhKW_cardNote",
|
|
102
|
+
"cardValue": "KgUhKW_cardValue",
|
|
103
|
+
"cards": "KgUhKW_cards",
|
|
104
|
+
"chart": "KgUhKW_chart",
|
|
105
|
+
"chartTitle": "KgUhKW_chartTitle",
|
|
106
|
+
"grid": "KgUhKW_grid",
|
|
107
|
+
"legend": "KgUhKW_legend",
|
|
108
|
+
"legendButton": "KgUhKW_legendButton",
|
|
109
|
+
"page": "KgUhKW_page",
|
|
110
|
+
"plot": "KgUhKW_plot",
|
|
111
|
+
"plotArea": "KgUhKW_plotArea",
|
|
112
|
+
"plotRow": "KgUhKW_plotRow",
|
|
113
|
+
"state": "KgUhKW_state",
|
|
114
|
+
"subtitle": "KgUhKW_subtitle",
|
|
115
|
+
"swatch": "KgUhKW_swatch",
|
|
116
|
+
"table": "KgUhKW_table",
|
|
117
|
+
"tableWrap": "KgUhKW_tableWrap",
|
|
118
|
+
"tickColumn": "KgUhKW_tickColumn",
|
|
119
|
+
"tickLabel": "KgUhKW_tickLabel",
|
|
120
|
+
"title": "KgUhKW_title",
|
|
121
|
+
"tooltip": "KgUhKW_tooltip"
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region lib/types/client/chart.js
|
|
125
|
+
/** Day buckets and stacked-bar geometry for the usage page. */
|
|
126
|
+
/** Local midnight of the day one time falls on. */
|
|
127
|
+
function midnight(at) {
|
|
128
|
+
return new Date(at.getFullYear(), at.getMonth(), at.getDate());
|
|
129
|
+
}
|
|
130
|
+
/** The local midnight after one midninght, one day later. */
|
|
131
|
+
function nextDay(day) {
|
|
132
|
+
return new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
|
|
133
|
+
}
|
|
134
|
+
/** Every series a bar stacks, in draw order. */
|
|
135
|
+
const USAGE_SERIES = [
|
|
136
|
+
"inputTokens",
|
|
137
|
+
"outputTokens",
|
|
138
|
+
"cacheReadTokens"
|
|
139
|
+
];
|
|
140
|
+
/**
|
|
141
|
+
* The local calendar day of one report time.
|
|
142
|
+
* @param at - report time in epoch milliseconds.
|
|
143
|
+
* @returns the local day as `YYYY-MM-DD`.
|
|
144
|
+
*/
|
|
145
|
+
function localDay(at) {
|
|
146
|
+
const date = new Date(at);
|
|
147
|
+
return `${String(date.getFullYear())}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Bucket records into local days, oldest first.
|
|
151
|
+
*
|
|
152
|
+
* Every day between the two ends is present, because a chart that closed a gap
|
|
153
|
+
* would draw a quiet week as a busy one.
|
|
154
|
+
* @param records - records the ledger answered with.
|
|
155
|
+
* @param from - first local day to include.
|
|
156
|
+
* @param to - last local day to include.
|
|
157
|
+
* @returns one bucket per day in the range.
|
|
158
|
+
*/
|
|
159
|
+
function usageByDay(records, from, to) {
|
|
160
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
161
|
+
for (const day of localDays(from, to)) buckets.set(day, {
|
|
162
|
+
day,
|
|
163
|
+
inputTokens: 0,
|
|
164
|
+
outputTokens: 0,
|
|
165
|
+
cacheReadTokens: 0,
|
|
166
|
+
cacheWriteTokens: 0
|
|
167
|
+
});
|
|
168
|
+
for (const record of records) {
|
|
169
|
+
const bucket = buckets.get(localDay(record.at));
|
|
170
|
+
if (bucket === void 0) continue;
|
|
171
|
+
bucket.inputTokens += record.inputTokens;
|
|
172
|
+
bucket.outputTokens += record.outputTokens;
|
|
173
|
+
bucket.cacheReadTokens += record.cacheReadTokens ?? 0;
|
|
174
|
+
bucket.cacheWriteTokens += record.cacheWriteTokens ?? 0;
|
|
175
|
+
}
|
|
176
|
+
return [...buckets.values()];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Every local day from one end of a range to the other, inclusive.
|
|
180
|
+
* @param from - first day to include.
|
|
181
|
+
* @param to - last day to include.
|
|
182
|
+
* @returns the day names in order.
|
|
183
|
+
*/
|
|
184
|
+
function localDays(from, to) {
|
|
185
|
+
const days = [];
|
|
186
|
+
for (let cursor = midnight(from); cursor.getTime() <= to.getTime(); cursor = nextDay(cursor)) days.push(localDay(cursor.getTime()));
|
|
187
|
+
return days;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Share of prompt tokens the provider served from its cache.
|
|
191
|
+
* @param cacheReadTokens - prompt tokens read from the cache.
|
|
192
|
+
* @param inputTokens - prompt tokens billed as uncached.
|
|
193
|
+
* @returns the rate between 0 and 1, and 0 when no prompt token was billed.
|
|
194
|
+
*/
|
|
195
|
+
function cacheHitRate(cacheReadTokens, inputTokens) {
|
|
196
|
+
const prompt = cacheReadTokens + inputTokens;
|
|
197
|
+
return prompt === 0 ? 0 : cacheReadTokens / prompt;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Total the buckets one summary is built from.
|
|
201
|
+
* @param records - records to sum.
|
|
202
|
+
* @returns the four buckets, their total, and the rate they imply.
|
|
203
|
+
*/
|
|
204
|
+
function summarize(records) {
|
|
205
|
+
let inputTokens = 0;
|
|
206
|
+
let outputTokens = 0;
|
|
207
|
+
let cacheReadTokens = 0;
|
|
208
|
+
let cacheWriteTokens = 0;
|
|
209
|
+
for (const record of records) {
|
|
210
|
+
inputTokens += record.inputTokens;
|
|
211
|
+
outputTokens += record.outputTokens;
|
|
212
|
+
cacheReadTokens += record.cacheReadTokens ?? 0;
|
|
213
|
+
cacheWriteTokens += record.cacheWriteTokens ?? 0;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
inputTokens,
|
|
217
|
+
outputTokens,
|
|
218
|
+
cacheReadTokens,
|
|
219
|
+
cacheWriteTokens,
|
|
220
|
+
totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
|
|
221
|
+
cacheHitRate: cacheHitRate(cacheReadTokens, inputTokens)
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Group records by the account they belong to, most expensive first.
|
|
226
|
+
* @param records - records a deployment-wide read carried.
|
|
227
|
+
* @returns one entry per account, with the buckets and rate of its records.
|
|
228
|
+
*/
|
|
229
|
+
function usageByAccount(records) {
|
|
230
|
+
const byAccount = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const record of records) {
|
|
232
|
+
const accountId = record.accountId ?? "—";
|
|
233
|
+
byAccount.set(accountId, [...byAccount.get(accountId) ?? [], record]);
|
|
234
|
+
}
|
|
235
|
+
return [...byAccount.entries()].map(([accountId, accountRecords]) => ({
|
|
236
|
+
accountId,
|
|
237
|
+
...summarize(accountRecords)
|
|
238
|
+
})).sort((left, right) => right.totalTokens - left.totalTokens);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Fold or unfold one series in a legend.
|
|
242
|
+
* @param hidden - series currently folded away.
|
|
243
|
+
* @param series - series the click named.
|
|
244
|
+
* @returns the next fold state, in `USAGE_SERIES` order.
|
|
245
|
+
*/
|
|
246
|
+
function toggleSeries(hidden, series) {
|
|
247
|
+
const next = hidden.includes(series) ? hidden.filter((entry) => entry !== series) : [...hidden, series];
|
|
248
|
+
return USAGE_SERIES.filter((entry) => next.includes(entry));
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Short token count a card, a tick, or a table cell shows.
|
|
252
|
+
* @param value - tokens to shorten.
|
|
253
|
+
* @returns the shortened count, without a unit suffix.
|
|
254
|
+
*/
|
|
255
|
+
function formatTokens(value) {
|
|
256
|
+
if (value < 1e3) return String(value);
|
|
257
|
+
if (value < 1e6) return `${(value / 1e3).toFixed(1)}k`;
|
|
258
|
+
return `${(value / 1e6).toFixed(1)}M`;
|
|
259
|
+
}
|
|
260
|
+
/** How many gridlines a chart draws. */
|
|
261
|
+
const TICKS = 4;
|
|
262
|
+
/**
|
|
263
|
+
* Lay one range of days out as stacked bars with a cache-hit-rate area behind
|
|
264
|
+
* them.
|
|
265
|
+
* @param days - day buckets, oldest first.
|
|
266
|
+
* @param options - canvas height, baseline, and the series folded away.
|
|
267
|
+
* @returns the bars, the area path, and the gridlines.
|
|
268
|
+
*/
|
|
269
|
+
function usageChart(days, options) {
|
|
270
|
+
const hidden = options.hidden ?? [];
|
|
271
|
+
const visible = USAGE_SERIES.filter((series) => !hidden.includes(series));
|
|
272
|
+
const peakTokens = Math.max(1, ...days.map((day) => visible.reduce((sum, series) => sum + day[series], 0)));
|
|
273
|
+
const plot = Math.max(1, options.baseline - options.height);
|
|
274
|
+
const step = days.length === 0 ? 0 : 100 / days.length;
|
|
275
|
+
const bars = days.map((day, index) => {
|
|
276
|
+
let offset = 0;
|
|
277
|
+
const bands = visible.map((series) => {
|
|
278
|
+
const height = day[series] / peakTokens * plot;
|
|
279
|
+
const band = {
|
|
280
|
+
series,
|
|
281
|
+
y: options.baseline - offset - height,
|
|
282
|
+
height
|
|
283
|
+
};
|
|
284
|
+
offset += height;
|
|
285
|
+
return band;
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
day: day.day,
|
|
289
|
+
x: index * step,
|
|
290
|
+
width: step * .62,
|
|
291
|
+
bands
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
const points = days.map((day, index) => {
|
|
295
|
+
const x = index * step + step * .31;
|
|
296
|
+
const y = options.baseline - cacheHitRate(day.cacheReadTokens, day.inputTokens) * plot;
|
|
297
|
+
return `${x.toFixed(2)},${y.toFixed(2)}`;
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
bars,
|
|
301
|
+
area: points.length === 0 ? "" : `M 0 ${String(options.baseline)} L ${points.join(" L ")} L 100 ${String(options.baseline)} Z`,
|
|
302
|
+
peakTokens,
|
|
303
|
+
ticks: Array.from({ length: 5 }, (_, index) => {
|
|
304
|
+
const share = index / TICKS;
|
|
305
|
+
return {
|
|
306
|
+
y: options.baseline - share * plot,
|
|
307
|
+
label: formatTokens(Math.round(peakTokens * share))
|
|
308
|
+
};
|
|
309
|
+
}),
|
|
310
|
+
baseline: options.baseline
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region lib/types/client/UsageSection.js
|
|
315
|
+
/** The usage page: metric cards, a foldable stacked bar chart with a hit-rate area, and a per-session table. */
|
|
316
|
+
/** Baseline of the plot in canvas units; the top of the plot is zero. */
|
|
317
|
+
const BASELINE = 100;
|
|
318
|
+
/** Fill of each stacked band, and of the hit-rate area. */
|
|
319
|
+
const BAND_FILL = {
|
|
320
|
+
inputTokens: "var(--qilin-alias-brand-primary)",
|
|
321
|
+
outputTokens: "var(--qilin-alias-state-success-secondary)",
|
|
322
|
+
cacheReadTokens: "var(--qilin-alias-state-business-primary)"
|
|
323
|
+
};
|
|
324
|
+
/** Share of some prompt traffic served from cache, as a percentage. */
|
|
325
|
+
function rate(value) {
|
|
326
|
+
return `${(value * 100).toFixed(value >= .1 || value === 0 ? 0 : 1)}%`;
|
|
327
|
+
}
|
|
328
|
+
/** The local first instant of the month one time falls in. */
|
|
329
|
+
function monthStart(at) {
|
|
330
|
+
return new Date(at.getFullYear(), at.getMonth(), 1);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Render what this account spent, by day and by session.
|
|
334
|
+
* @param props - the Settings section seats: runtime data and the page's dictionary.
|
|
335
|
+
*/
|
|
336
|
+
function UsageSection({ t }) {
|
|
337
|
+
const [answer, setAnswer] = (0, react.useState)(void 0);
|
|
338
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
339
|
+
const [hidden, setHidden] = (0, react.useState)([]);
|
|
340
|
+
const [areaHidden, setAreaHidden] = (0, react.useState)(false);
|
|
341
|
+
const [active, setActive] = (0, react.useState)(void 0);
|
|
342
|
+
const bars = (0, react.useRef)([]);
|
|
343
|
+
(0, react.useEffect)(() => {
|
|
344
|
+
const controller = new AbortController();
|
|
345
|
+
const from = monthStart(/* @__PURE__ */ new Date()).getTime();
|
|
346
|
+
(async () => {
|
|
347
|
+
try {
|
|
348
|
+
const response = await fetch(`api/usage/me?from=${String(from)}`, { signal: controller.signal });
|
|
349
|
+
if (!response.ok) throw new Error(`the usage read answered HTTP ${String(response.status)}`);
|
|
350
|
+
setAnswer(await response.json());
|
|
351
|
+
} catch {
|
|
352
|
+
if (!controller.signal.aborted) setFailed(true);
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
return () => {
|
|
356
|
+
controller.abort();
|
|
357
|
+
};
|
|
358
|
+
}, []);
|
|
359
|
+
if (failed) return (0, react_jsx_runtime.jsx)("p", {
|
|
360
|
+
className: UsageSection_module_css_default.state,
|
|
361
|
+
children: t("unavailable")
|
|
362
|
+
});
|
|
363
|
+
if (answer === void 0) return (0, react_jsx_runtime.jsx)("p", {
|
|
364
|
+
className: UsageSection_module_css_default.state,
|
|
365
|
+
children: t("loading")
|
|
366
|
+
});
|
|
367
|
+
const now = /* @__PURE__ */ new Date();
|
|
368
|
+
const records = answer.records ?? [];
|
|
369
|
+
const days = usageByDay(records, monthStart(now), now);
|
|
370
|
+
const chart = usageChart(days, {
|
|
371
|
+
height: 0,
|
|
372
|
+
baseline: BASELINE,
|
|
373
|
+
hidden
|
|
374
|
+
});
|
|
375
|
+
const todayTime = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
376
|
+
const month = summarize(records);
|
|
377
|
+
const today = summarize(records.filter((record) => record.at >= todayTime));
|
|
378
|
+
const all = answer.total;
|
|
379
|
+
const allTokens = all === void 0 ? 0 : all.inputTokens + all.outputTokens + all.cacheReadTokens + all.cacheWriteTokens;
|
|
380
|
+
/** The administrator reads the deployment; everyone else reads their own account. */
|
|
381
|
+
const deployment = answer.scope === "deployment";
|
|
382
|
+
/** Rows the table breaks the month into: one per account, or one per session. */
|
|
383
|
+
const rows = deployment ? usageByAccount(records).map((entry) => ({
|
|
384
|
+
key: entry.accountId,
|
|
385
|
+
...entry
|
|
386
|
+
})) : sessionRows(records);
|
|
387
|
+
const accounts = Object.keys(answer.totals ?? {}).length;
|
|
388
|
+
const label = {
|
|
389
|
+
inputTokens: t("metric.input"),
|
|
390
|
+
outputTokens: t("metric.output"),
|
|
391
|
+
cacheReadTokens: t("metric.cacheRead")
|
|
392
|
+
};
|
|
393
|
+
const activeDay = days.find((entry) => entry.day === active);
|
|
394
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
395
|
+
className: UsageSection_module_css_default.page,
|
|
396
|
+
children: [
|
|
397
|
+
(0, react_jsx_runtime.jsxs)("header", { children: [(0, react_jsx_runtime.jsx)("h2", {
|
|
398
|
+
className: UsageSection_module_css_default.title,
|
|
399
|
+
children: t("title")
|
|
400
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
401
|
+
className: UsageSection_module_css_default.subtitle,
|
|
402
|
+
children: deployment ? t("scope.deployment") : t("subtitle", { account: answer.accountId ?? "—" })
|
|
403
|
+
})] }),
|
|
404
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
405
|
+
className: UsageSection_module_css_default.cards,
|
|
406
|
+
children: [
|
|
407
|
+
(0, react_jsx_runtime.jsx)(Card, {
|
|
408
|
+
label: t("today"),
|
|
409
|
+
value: formatTokens(today.totalTokens),
|
|
410
|
+
note: rate(today.cacheHitRate)
|
|
411
|
+
}),
|
|
412
|
+
(0, react_jsx_runtime.jsx)(Card, {
|
|
413
|
+
label: t("month"),
|
|
414
|
+
value: formatTokens(month.totalTokens),
|
|
415
|
+
note: rate(month.cacheHitRate)
|
|
416
|
+
}),
|
|
417
|
+
(0, react_jsx_runtime.jsx)(Card, {
|
|
418
|
+
label: t("all"),
|
|
419
|
+
value: formatTokens(allTokens),
|
|
420
|
+
note: rate(all?.cacheHitRate ?? 0)
|
|
421
|
+
}),
|
|
422
|
+
(0, react_jsx_runtime.jsx)(Card, {
|
|
423
|
+
label: t("metric.turns"),
|
|
424
|
+
value: String(all?.turns ?? 0),
|
|
425
|
+
note: deployment ? `${t("table.account")} ${String(accounts)}` : `${t("metric.sessions")} ${String(all?.sessions ?? 0)}`
|
|
426
|
+
})
|
|
427
|
+
]
|
|
428
|
+
}),
|
|
429
|
+
(0, react_jsx_runtime.jsxs)("section", {
|
|
430
|
+
className: UsageSection_module_css_default.chart,
|
|
431
|
+
children: [
|
|
432
|
+
(0, react_jsx_runtime.jsx)("h3", {
|
|
433
|
+
className: UsageSection_module_css_default.chartTitle,
|
|
434
|
+
children: t("chart.title")
|
|
435
|
+
}),
|
|
436
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
437
|
+
className: UsageSection_module_css_default.caption,
|
|
438
|
+
children: t("chart.caption")
|
|
439
|
+
}),
|
|
440
|
+
answer.truncated === true ? (0, react_jsx_runtime.jsx)("p", {
|
|
441
|
+
className: UsageSection_module_css_default.cardNote,
|
|
442
|
+
children: t("truncated")
|
|
443
|
+
}) : null,
|
|
444
|
+
records.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
445
|
+
className: UsageSection_module_css_default.state,
|
|
446
|
+
children: t("chart.empty")
|
|
447
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
448
|
+
className: UsageSection_module_css_default.plotRow,
|
|
449
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
450
|
+
className: UsageSection_module_css_default.tickColumn,
|
|
451
|
+
"aria-hidden": "true",
|
|
452
|
+
children: chart.ticks.map((tick) => (0, react_jsx_runtime.jsx)("span", {
|
|
453
|
+
className: UsageSection_module_css_default.tickLabel,
|
|
454
|
+
style: { top: `${String(tick.y)}%` },
|
|
455
|
+
children: tick.label
|
|
456
|
+
}, tick.label + String(tick.y)))
|
|
457
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
458
|
+
className: UsageSection_module_css_default.plotArea,
|
|
459
|
+
children: [(0, react_jsx_runtime.jsxs)("svg", {
|
|
460
|
+
className: UsageSection_module_css_default.plot,
|
|
461
|
+
viewBox: "0 0 100 100",
|
|
462
|
+
preserveAspectRatio: "none",
|
|
463
|
+
role: "img",
|
|
464
|
+
"aria-label": t("chart.title"),
|
|
465
|
+
children: [
|
|
466
|
+
chart.ticks.map((tick) => (0, react_jsx_runtime.jsx)("line", {
|
|
467
|
+
className: UsageSection_module_css_default.grid,
|
|
468
|
+
x1: 0,
|
|
469
|
+
x2: 100,
|
|
470
|
+
y1: tick.y,
|
|
471
|
+
y2: tick.y
|
|
472
|
+
}, tick.label + String(tick.y))),
|
|
473
|
+
areaHidden ? null : (0, react_jsx_runtime.jsx)("path", {
|
|
474
|
+
className: UsageSection_module_css_default.area,
|
|
475
|
+
d: chart.area
|
|
476
|
+
}),
|
|
477
|
+
chart.bars.map((bar, index) => (0, react_jsx_runtime.jsxs)("g", {
|
|
478
|
+
ref: (element) => {
|
|
479
|
+
bars.current[index] = element;
|
|
480
|
+
},
|
|
481
|
+
className: UsageSection_module_css_default.bar,
|
|
482
|
+
tabIndex: 0,
|
|
483
|
+
role: "button",
|
|
484
|
+
"aria-label": `${bar.day} ${label.cacheReadTokens}`,
|
|
485
|
+
onMouseEnter: () => {
|
|
486
|
+
setActive(bar.day);
|
|
487
|
+
},
|
|
488
|
+
onMouseLeave: () => {
|
|
489
|
+
setActive(void 0);
|
|
490
|
+
},
|
|
491
|
+
onFocus: () => {
|
|
492
|
+
setActive(bar.day);
|
|
493
|
+
},
|
|
494
|
+
onBlur: () => {
|
|
495
|
+
setActive(void 0);
|
|
496
|
+
},
|
|
497
|
+
onKeyDown: (event) => {
|
|
498
|
+
if (event.key === "ArrowRight") bars.current[Math.min(index + 1, chart.bars.length - 1)]?.focus();
|
|
499
|
+
if (event.key === "ArrowLeft") bars.current[Math.max(index - 1, 0)]?.focus();
|
|
500
|
+
if (event.key === "Escape") setActive(void 0);
|
|
501
|
+
},
|
|
502
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
503
|
+
x: bar.x,
|
|
504
|
+
y: 0,
|
|
505
|
+
width: bar.width,
|
|
506
|
+
height: BASELINE,
|
|
507
|
+
fill: "transparent"
|
|
508
|
+
}), bar.bands.map((band) => (0, react_jsx_runtime.jsx)("rect", {
|
|
509
|
+
className: UsageSection_module_css_default.band,
|
|
510
|
+
x: bar.x,
|
|
511
|
+
y: band.y,
|
|
512
|
+
width: bar.width,
|
|
513
|
+
height: band.height,
|
|
514
|
+
fill: BAND_FILL[band.series]
|
|
515
|
+
}, band.series))]
|
|
516
|
+
}, bar.day))
|
|
517
|
+
]
|
|
518
|
+
}), activeDay === void 0 ? null : (0, react_jsx_runtime.jsxs)("div", {
|
|
519
|
+
className: UsageSection_module_css_default.tooltip,
|
|
520
|
+
style: { left: `${((days.findIndex((entry) => entry.day === activeDay.day) + .5) * (100 / Math.max(1, days.length))).toFixed(2)}%` },
|
|
521
|
+
children: [
|
|
522
|
+
activeDay.day,
|
|
523
|
+
" · ",
|
|
524
|
+
label.inputTokens,
|
|
525
|
+
" ",
|
|
526
|
+
formatTokens(activeDay.inputTokens),
|
|
527
|
+
" · ",
|
|
528
|
+
label.outputTokens,
|
|
529
|
+
" ",
|
|
530
|
+
formatTokens(activeDay.outputTokens),
|
|
531
|
+
" · ",
|
|
532
|
+
label.cacheReadTokens,
|
|
533
|
+
" ",
|
|
534
|
+
formatTokens(activeDay.cacheReadTokens),
|
|
535
|
+
" · ",
|
|
536
|
+
t("metric.hitRate"),
|
|
537
|
+
" ",
|
|
538
|
+
rate(cacheHitRate(activeDay.cacheReadTokens, activeDay.inputTokens))
|
|
539
|
+
]
|
|
540
|
+
})]
|
|
541
|
+
})]
|
|
542
|
+
}),
|
|
543
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
544
|
+
className: UsageSection_module_css_default.legend,
|
|
545
|
+
children: [USAGE_SERIES.map((series) => (0, react_jsx_runtime.jsxs)("button", {
|
|
546
|
+
type: "button",
|
|
547
|
+
className: UsageSection_module_css_default.legendButton,
|
|
548
|
+
"aria-pressed": !hidden.includes(series),
|
|
549
|
+
title: hidden.includes(series) ? t("chart.unfold", { series: label[series] }) : t("chart.fold", { series: label[series] }),
|
|
550
|
+
onClick: () => {
|
|
551
|
+
setHidden(toggleSeries(hidden, series));
|
|
552
|
+
},
|
|
553
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
554
|
+
className: UsageSection_module_css_default.swatch,
|
|
555
|
+
style: { background: BAND_FILL[series] }
|
|
556
|
+
}), label[series]]
|
|
557
|
+
}, series)), (0, react_jsx_runtime.jsxs)("button", {
|
|
558
|
+
type: "button",
|
|
559
|
+
className: UsageSection_module_css_default.legendButton,
|
|
560
|
+
"aria-pressed": !areaHidden,
|
|
561
|
+
onClick: () => {
|
|
562
|
+
setAreaHidden(!areaHidden);
|
|
563
|
+
},
|
|
564
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
565
|
+
className: UsageSection_module_css_default.swatch,
|
|
566
|
+
style: { background: "var(--qilin-alias-brand-primary)" }
|
|
567
|
+
}), t("metric.hitRate")]
|
|
568
|
+
})]
|
|
569
|
+
})
|
|
570
|
+
]
|
|
571
|
+
}),
|
|
572
|
+
rows.length === 0 ? null : (0, react_jsx_runtime.jsx)("div", {
|
|
573
|
+
className: UsageSection_module_css_default.tableWrap,
|
|
574
|
+
children: (0, react_jsx_runtime.jsxs)("table", {
|
|
575
|
+
className: UsageSection_module_css_default.table,
|
|
576
|
+
children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
577
|
+
(0, react_jsx_runtime.jsx)("th", { children: deployment ? t("table.account") : t("table.session") }),
|
|
578
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("table.input") }),
|
|
579
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("table.output") }),
|
|
580
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("table.cacheRead") }),
|
|
581
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("table.hitRate") }),
|
|
582
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("table.total") })
|
|
583
|
+
] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row) => (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
584
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
585
|
+
title: row.key,
|
|
586
|
+
children: row.key.slice(0, 8)
|
|
587
|
+
}),
|
|
588
|
+
(0, react_jsx_runtime.jsx)("td", { children: formatTokens(row.inputTokens) }),
|
|
589
|
+
(0, react_jsx_runtime.jsx)("td", { children: formatTokens(row.outputTokens) }),
|
|
590
|
+
(0, react_jsx_runtime.jsx)("td", { children: formatTokens(row.cacheReadTokens) }),
|
|
591
|
+
(0, react_jsx_runtime.jsx)("td", { children: rate(row.cacheHitRate) }),
|
|
592
|
+
(0, react_jsx_runtime.jsx)("td", { children: formatTokens(row.totalTokens) })
|
|
593
|
+
] }, row.key)) })]
|
|
594
|
+
})
|
|
595
|
+
})
|
|
596
|
+
]
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
/** One session's usage this month, most expensive first. */
|
|
600
|
+
function sessionRows(records) {
|
|
601
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
602
|
+
for (const record of records) bySession.set(record.sessionId, [...bySession.get(record.sessionId) ?? [], record]);
|
|
603
|
+
return [...bySession.entries()].map(([key, sessionRecords]) => ({
|
|
604
|
+
key,
|
|
605
|
+
...summarize(sessionRecords)
|
|
606
|
+
})).sort((left, right) => right.totalTokens - left.totalTokens);
|
|
607
|
+
}
|
|
608
|
+
/** One metric card. */
|
|
609
|
+
function Card({ label, value, note }) {
|
|
610
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
611
|
+
className: UsageSection_module_css_default.card,
|
|
612
|
+
children: [
|
|
613
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
614
|
+
className: UsageSection_module_css_default.cardLabel,
|
|
615
|
+
children: label
|
|
616
|
+
}),
|
|
617
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
618
|
+
className: UsageSection_module_css_default.cardValue,
|
|
619
|
+
children: value
|
|
620
|
+
}),
|
|
621
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
622
|
+
className: UsageSection_module_css_default.cardNote,
|
|
623
|
+
children: note
|
|
624
|
+
})
|
|
625
|
+
]
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region lib/types/client/index.js
|
|
630
|
+
/** Settings section showing one account the tokens it spent. */
|
|
631
|
+
/** Services the page reads: the slot registry and its dictionaries. */
|
|
632
|
+
const inject = ["slots", "locale"];
|
|
633
|
+
/** Order the Usage section takes among the settings sections. */
|
|
634
|
+
const ORDER = 25;
|
|
635
|
+
/**
|
|
636
|
+
* Register the usage page as a Settings section.
|
|
637
|
+
* @param ctx - browser context carrying the slot registry and dictionaries.
|
|
638
|
+
*/
|
|
639
|
+
function apply(ctx) {
|
|
640
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
641
|
+
zh,
|
|
642
|
+
en
|
|
643
|
+
}), "ui-usage: dictionaries");
|
|
644
|
+
const t = ctx.locale.bind(NS);
|
|
645
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
646
|
+
name: "settings.section",
|
|
647
|
+
id: "usage",
|
|
648
|
+
order: ORDER,
|
|
649
|
+
label: () => t("nav"),
|
|
650
|
+
locale: NS
|
|
651
|
+
}, UsageSection));
|
|
652
|
+
}
|
|
653
|
+
//#endregion
|
|
654
|
+
exports.apply = apply;
|
|
655
|
+
exports.inject = inject;
|
|
656
|
+
return module.exports;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The usage page: metric cards, a foldable stacked bar chart with a hit-rate area, and a per-session table. */
|
|
2
|
+
import type { PropsLocale, PropsRuntime } from '@qilin-agent/client-ui-slots';
|
|
3
|
+
/** Props of the usage page, as its Settings registration supplies them. */
|
|
4
|
+
export type UsageSectionComponentProps = PropsRuntime<'settings.section'> & PropsLocale<'usage'>;
|
|
5
|
+
/**
|
|
6
|
+
* Render what this account spent, by day and by session.
|
|
7
|
+
* @param props - the Settings section seats: runtime data and the page's dictionary.
|
|
8
|
+
*/
|
|
9
|
+
export declare function UsageSection({ t }: UsageSectionComponentProps): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=UsageSection.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** Day buckets and stacked-bar geometry for the usage page. */
|
|
2
|
+
/** One Turn as the deployment's ledger reports it. */
|
|
3
|
+
export interface UsageRecord {
|
|
4
|
+
/** Account the Turn belongs to; present only in a deployment-wide read. */
|
|
5
|
+
readonly accountId?: string;
|
|
6
|
+
/** Session the Turn belongs to. */
|
|
7
|
+
readonly sessionId: string;
|
|
8
|
+
/** Turn number inside that session. */
|
|
9
|
+
readonly turn: number;
|
|
10
|
+
/** Prompt tokens billed as uncached input. */
|
|
11
|
+
readonly inputTokens: number;
|
|
12
|
+
/** Completion tokens billed for the Turn. */
|
|
13
|
+
readonly outputTokens: number;
|
|
14
|
+
/** Prompt tokens the provider served from its cache, when it reported them. */
|
|
15
|
+
readonly cacheReadTokens?: number;
|
|
16
|
+
/** Prompt tokens written to that cache, when it reported them. */
|
|
17
|
+
readonly cacheWriteTokens?: number;
|
|
18
|
+
/** Report time in epoch milliseconds. */
|
|
19
|
+
readonly at: number;
|
|
20
|
+
}
|
|
21
|
+
/** One local day's usage, the unit a bar is drawn from. */
|
|
22
|
+
export interface UsageDay {
|
|
23
|
+
/** Local calendar day, `YYYY-MM-DD`. */
|
|
24
|
+
readonly day: string;
|
|
25
|
+
readonly inputTokens: number;
|
|
26
|
+
readonly outputTokens: number;
|
|
27
|
+
readonly cacheReadTokens: number;
|
|
28
|
+
readonly cacheWriteTokens: number;
|
|
29
|
+
}
|
|
30
|
+
/** The series a bar stacks, bottom first. */
|
|
31
|
+
export type UsageSeries = 'inputTokens' | 'outputTokens' | 'cacheReadTokens';
|
|
32
|
+
/** Every series a bar stacks, in draw order. */
|
|
33
|
+
export declare const USAGE_SERIES: readonly UsageSeries[];
|
|
34
|
+
/** Five metrics one day or one ledger total answers with. */
|
|
35
|
+
export interface UsageSummary {
|
|
36
|
+
readonly inputTokens: number;
|
|
37
|
+
readonly outputTokens: number;
|
|
38
|
+
readonly cacheReadTokens: number;
|
|
39
|
+
readonly cacheWriteTokens: number;
|
|
40
|
+
readonly totalTokens: number;
|
|
41
|
+
/** Share of prompt tokens served from cache, between 0 and 1. */
|
|
42
|
+
readonly cacheHitRate: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The local calendar day of one report time.
|
|
46
|
+
* @param at - report time in epoch milliseconds.
|
|
47
|
+
* @returns the local day as `YYYY-MM-DD`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function localDay(at: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* Bucket records into local days, oldest first.
|
|
52
|
+
*
|
|
53
|
+
* Every day between the two ends is present, because a chart that closed a gap
|
|
54
|
+
* would draw a quiet week as a busy one.
|
|
55
|
+
* @param records - records the ledger answered with.
|
|
56
|
+
* @param from - first local day to include.
|
|
57
|
+
* @param to - last local day to include.
|
|
58
|
+
* @returns one bucket per day in the range.
|
|
59
|
+
*/
|
|
60
|
+
export declare function usageByDay(records: readonly UsageRecord[], from: Date, to: Date): UsageDay[];
|
|
61
|
+
/**
|
|
62
|
+
* Every local day from one end of a range to the other, inclusive.
|
|
63
|
+
* @param from - first day to include.
|
|
64
|
+
* @param to - last day to include.
|
|
65
|
+
* @returns the day names in order.
|
|
66
|
+
*/
|
|
67
|
+
export declare function localDays(from: Date, to: Date): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Share of prompt tokens the provider served from its cache.
|
|
70
|
+
* @param cacheReadTokens - prompt tokens read from the cache.
|
|
71
|
+
* @param inputTokens - prompt tokens billed as uncached.
|
|
72
|
+
* @returns the rate between 0 and 1, and 0 when no prompt token was billed.
|
|
73
|
+
*/
|
|
74
|
+
export declare function cacheHitRate(cacheReadTokens: number, inputTokens: number): number;
|
|
75
|
+
/**
|
|
76
|
+
* Total the buckets one summary is built from.
|
|
77
|
+
* @param records - records to sum.
|
|
78
|
+
* @returns the four buckets, their total, and the rate they imply.
|
|
79
|
+
*/
|
|
80
|
+
export declare function summarize(records: readonly UsageRecord[]): UsageSummary;
|
|
81
|
+
/** One account's usage inside the records a read carried. */
|
|
82
|
+
export interface UsageAccount extends UsageSummary {
|
|
83
|
+
/** Account the records belong to, or the placeholder a tagless record groups under. */
|
|
84
|
+
readonly accountId: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Group records by the account they belong to, most expensive first.
|
|
88
|
+
* @param records - records a deployment-wide read carried.
|
|
89
|
+
* @returns one entry per account, with the buckets and rate of its records.
|
|
90
|
+
*/
|
|
91
|
+
export declare function usageByAccount(records: readonly UsageRecord[]): UsageAccount[];
|
|
92
|
+
/**
|
|
93
|
+
* Fold or unfold one series in a legend.
|
|
94
|
+
* @param hidden - series currently folded away.
|
|
95
|
+
* @param series - series the click named.
|
|
96
|
+
* @returns the next fold state, in `USAGE_SERIES` order.
|
|
97
|
+
*/
|
|
98
|
+
export declare function toggleSeries(hidden: readonly UsageSeries[], series: UsageSeries): UsageSeries[];
|
|
99
|
+
/**
|
|
100
|
+
* Short token count a card, a tick, or a table cell shows.
|
|
101
|
+
* @param value - tokens to shorten.
|
|
102
|
+
* @returns the shortened count, without a unit suffix.
|
|
103
|
+
*/
|
|
104
|
+
export declare function formatTokens(value: number): string;
|
|
105
|
+
/** One stacked bar, in canvas coordinates. */
|
|
106
|
+
export interface UsageBar {
|
|
107
|
+
readonly day: string;
|
|
108
|
+
readonly x: number;
|
|
109
|
+
readonly width: number;
|
|
110
|
+
readonly bands: readonly {
|
|
111
|
+
series: UsageSeries;
|
|
112
|
+
y: number;
|
|
113
|
+
height: number;
|
|
114
|
+
}[];
|
|
115
|
+
}
|
|
116
|
+
/** Everything one chart render draws. */
|
|
117
|
+
export interface UsageChart {
|
|
118
|
+
readonly bars: readonly UsageBar[];
|
|
119
|
+
/** SVG path of the hit-rate area behind the bars. */
|
|
120
|
+
readonly area: string;
|
|
121
|
+
/** Largest stacked total, before folding, in tokens. */
|
|
122
|
+
readonly peakTokens: number;
|
|
123
|
+
/** Horizontal gridlines with their token labels, bottom first. */
|
|
124
|
+
readonly ticks: readonly {
|
|
125
|
+
y: number;
|
|
126
|
+
label: string;
|
|
127
|
+
}[];
|
|
128
|
+
/** Baseline every bar stands on, in canvas coordinates. */
|
|
129
|
+
readonly baseline: number;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Lay one range of days out as stacked bars with a cache-hit-rate area behind
|
|
133
|
+
* them.
|
|
134
|
+
* @param days - day buckets, oldest first.
|
|
135
|
+
* @param options - canvas height, baseline, and the series folded away.
|
|
136
|
+
* @returns the bars, the area path, and the gridlines.
|
|
137
|
+
*/
|
|
138
|
+
export declare function usageChart(days: readonly UsageDay[], options: {
|
|
139
|
+
height: number;
|
|
140
|
+
baseline: number;
|
|
141
|
+
hidden?: readonly UsageSeries[];
|
|
142
|
+
}): UsageChart;
|
|
143
|
+
//# sourceMappingURL=chart.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Settings section showing one account the tokens it spent. */
|
|
2
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
3
|
+
import { zh } from './locales.ts';
|
|
4
|
+
declare module '@qilin-agent/client-ui-slots' {
|
|
5
|
+
interface LocaleNamespaceMap {
|
|
6
|
+
/** Usage page copy. */
|
|
7
|
+
usage: keyof typeof zh;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Services the page reads: the slot registry and its dictionaries. */
|
|
11
|
+
export declare const inject: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Register the usage page as a Settings section.
|
|
14
|
+
* @param ctx - browser context carrying the slot registry and dictionaries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function apply(ctx: Context): void;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Copy dictionaries for the usage page. */
|
|
2
|
+
/** Dictionary namespace owned by this plugin. */
|
|
3
|
+
export declare const NS = "usage";
|
|
4
|
+
/** English strings (the key-set source of truth for this pair). */
|
|
5
|
+
export declare const en: {
|
|
6
|
+
readonly nav: "Usage";
|
|
7
|
+
readonly title: "Token usage";
|
|
8
|
+
readonly subtitle: "What this account spent, by day and by session. For account {account}.";
|
|
9
|
+
readonly 'scope.deployment': "Deployment-wide, every account";
|
|
10
|
+
readonly 'table.account': "Account";
|
|
11
|
+
readonly 'metric.input': "Uncached input";
|
|
12
|
+
readonly 'metric.output': "Output";
|
|
13
|
+
readonly 'metric.cacheRead': "Cache reads";
|
|
14
|
+
readonly 'metric.cacheWrite': "Cache writes";
|
|
15
|
+
readonly 'metric.total': "Total";
|
|
16
|
+
readonly 'metric.hitRate': "Cache hit rate";
|
|
17
|
+
readonly 'metric.sessions': "Sessions";
|
|
18
|
+
readonly 'metric.turns': "Turns";
|
|
19
|
+
readonly today: "Today";
|
|
20
|
+
readonly month: "This month";
|
|
21
|
+
readonly all: "All time";
|
|
22
|
+
readonly 'chart.title': "Token usage by day this month";
|
|
23
|
+
readonly 'chart.caption': "Bars stack the tokens spent each day; the area behind them is the share of prompt tokens served from cache.";
|
|
24
|
+
readonly 'chart.empty': "No usage has been reported yet.";
|
|
25
|
+
readonly 'chart.fold': "Fold {series}";
|
|
26
|
+
readonly 'chart.unfold': "Unfold {series}";
|
|
27
|
+
readonly 'table.title': "By session";
|
|
28
|
+
readonly 'table.session': "Session";
|
|
29
|
+
readonly 'table.input': "Input";
|
|
30
|
+
readonly 'table.output': "Output";
|
|
31
|
+
readonly 'table.cacheRead': "Cache read";
|
|
32
|
+
readonly 'table.hitRate': "Hit rate";
|
|
33
|
+
readonly 'table.total': "Total";
|
|
34
|
+
readonly 'table.sort': "Sort by this column";
|
|
35
|
+
readonly loading: "Reading this account’s usage…";
|
|
36
|
+
readonly unavailable: "The deployment could not be reached, so this page has nothing to show.";
|
|
37
|
+
readonly truncated: "Only the most recent records are shown.";
|
|
38
|
+
};
|
|
39
|
+
/** Chinese strings for the same key set. */
|
|
40
|
+
export declare const zh: Record<keyof typeof en, string>;
|
|
41
|
+
//# sourceMappingURL=locales.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/client-ui-usage",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Per-account token usage in Settings: daily stacked bars, cache-hit-rate area, and per-session detail",
|
|
4
|
+
"version": "3.1.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
11
|
+
"directory": "packages/client/ui-usage"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/types/index.d.ts",
|
|
22
|
+
"default": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"types": "./lib/types/client/index.d.ts",
|
|
26
|
+
"default": "./lib/client.js"
|
|
27
|
+
},
|
|
28
|
+
"./src/*": "./src/*",
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"lib/index.js",
|
|
33
|
+
"lib/client.js",
|
|
34
|
+
"lib/types/**/*.d.ts"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"qilin": {
|
|
38
|
+
"client": {
|
|
39
|
+
"inject": [
|
|
40
|
+
"@qilin-agent/client-locale",
|
|
41
|
+
"@qilin-agent/client-ui-layout",
|
|
42
|
+
"@qilin-agent/client-ui-settings"
|
|
43
|
+
],
|
|
44
|
+
"platform": "web"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "~18.3.1",
|
|
52
|
+
"clsx": "^2.1.1",
|
|
53
|
+
"react": "^18.2.0",
|
|
54
|
+
"@qilin-agent/client-locale": "3.1.5",
|
|
55
|
+
"@qilin-agent/client-store": "3.1.5",
|
|
56
|
+
"@qilin-agent/kylin": "~4.0.3",
|
|
57
|
+
"@qilin-agent/client-test-runtime": "3.1.5",
|
|
58
|
+
"@qilin-agent/client-ui-primitives": "3.1.5",
|
|
59
|
+
"@qilin-agent/client-ui-layout": "3.1.5",
|
|
60
|
+
"@qilin-agent/client-ui-renderer": "3.1.5",
|
|
61
|
+
"@qilin-agent/client-ui-settings": "3.1.5",
|
|
62
|
+
"@qilin-agent/client-ui-slots": "3.1.5"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"bundle": "tsdown",
|
|
66
|
+
"watch": "tsdown --watch"
|
|
67
|
+
}
|
|
6
68
|
}
|