dsh-connect-minimaxcode 0.2.3 → 0.3.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.md +82 -27
- package/lib/client.js +199 -0
- package/lib/index.d.ts +275 -3
- package/lib/index.js +603 -19
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
|
|
42
42
|
- **零配置**:自动读取 MiniMax Code 桌面 App 的登录态,不在 DSH 中存储任何明文 Token
|
|
43
43
|
- **动态模型目录**:从上游接口实时拉取,模型增减无需更新插件
|
|
44
|
+
- **积分余额可见**:在对话框底部的胶囊里显示 MiniMax Code 的剩余积分,以及本机累计消耗
|
|
45
|
+
- **实时消耗统计**:每次请求的 token 用量取自上游响应本身,可按官方价目表折算等效金额
|
|
44
46
|
- **复用官方适配器**:上游为标准 Anthropic Messages 协议,直接复用 DSH 自带 provider,工具调用、推理块、上下文压缩均由 DSH 托管
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
+
- **只读为主**:除「续期已过期的桌面 App 登录态」外,不写入、不修改 MiniMax 的任何文件,也不执行任何登录动作
|
|
48
|
+
- **界面不打扰**:胶囊只在输入框下方的 dock 里占一行,不改动 DSH 的其它界面
|
|
47
49
|
|
|
48
50
|
## 前置条件
|
|
49
51
|
|
|
@@ -70,6 +72,8 @@ Web profile 把 `--profile desktop` 换成 `--profile web`。
|
|
|
70
72
|
|
|
71
73
|
安装后**无需重启**,插件会热加载;模型随后出现在模型选择器的 **MiniMax Code** 分组中。
|
|
72
74
|
|
|
75
|
+
> 界面胶囊(积分余额)属于浏览器侧插件,**首次安装或升级到 0.3.0 后需要重启一次 DSH** 才会出现在输入框下方。
|
|
76
|
+
|
|
73
77
|
### 若安装被 `allowBuilds` 拦截
|
|
74
78
|
|
|
75
79
|
本仓库把构建产物 `lib/` 一并提交,因此正常情况下安装不会触发构建。若你看到:
|
|
@@ -97,15 +101,24 @@ Web profile 把 `--profile desktop` 换成 `--profile web`。
|
|
|
97
101
|
直接使用即可。工具调用由 DSH 本地工具执行,插件只负责提供模型通路。
|
|
98
102
|
|
|
99
103
|
5. **令牌过期时重新登录**
|
|
100
|
-
MiniMax Code
|
|
104
|
+
MiniMax Code 有**两份互不通用**的登录态,本插件各用其一:
|
|
105
|
+
|
|
106
|
+
| 凭证 | 位置 | 有效期 | 用途 |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| JWT | `~/.minimax/local-runtime.auth.json` | 约 15 天 | 调模型网关(`/mavis/api/v1/llm/v1/messages`) |
|
|
109
|
+
| OAuth(`mmoat_`) | `~/.minimax/auth/prod/<region>/mcode-public/auth.json` | 约 1 小时 | 读账户与积分(`/matrix/api/v1/commerce/get_membership_info`) |
|
|
110
|
+
|
|
111
|
+
两份凭证互不通用:拿 JWT 打账户接口一律 `401`,拿 OAuth 令牌打模型网关同样不通。**模型对话**用 JWT,**积分显示**用 OAuth。
|
|
101
112
|
|
|
102
113
|
| 现象 | 含义 | 处理方式 |
|
|
103
114
|
|---|---|---|
|
|
104
|
-
| 正常使用 |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
115
|
+
| 正常使用 | 两份凭证都有效 | — |
|
|
116
|
+
| 模型报鉴权错误 | JWT 已失效 | 在 MiniMax Code 中**重新登录**,然后重启 DSH |
|
|
117
|
+
| 积分胶囊显示「登录态已过期」 | OAuth 令牌过期 | 插件会用 App 自己的 refresh token 续期并写回;若续期失败,请打开一次 MiniMax Code 桌面 App 让它自行续期 |
|
|
107
118
|
|
|
108
|
-
>
|
|
119
|
+
> OAuth 令牌过期**不会**影响模型对话:JWT 仍然有效,模型照常可用,只是积分胶囊暂时拿不到余额。
|
|
120
|
+
>
|
|
121
|
+
> 续期会**轮换** refresh token。插件因此把新的一对令牌按客户端**原格式原子写回**同一个文件,并保持 `generation` 不变——客户端靠它判断「这份凭据是否仍属于本次登录」,擅自抬高会让桌面 App 误判为异地登录。
|
|
109
122
|
|
|
110
123
|
### 关于响应速度
|
|
111
124
|
|
|
@@ -122,6 +135,8 @@ MiniMax 的这些模型**强制要求先推理再输出**,思考无法关闭
|
|
|
122
135
|
1. 读取 `~/.minimax/local-runtime.auth.json`(MiniMax Code 写入的明文登录信息),**仅在内存中**解析其 JWT
|
|
123
136
|
2. 调用 `GET /mavis/api/v1/models` 获取实时模型目录(含上下文长度、多模态、工具调用等能力声明)
|
|
124
137
|
3. 注册 `minimaxcode` provider,指向 `POST /mavis/api/v1/llm/v1/messages`(标准 Anthropic Messages 协议)
|
|
138
|
+
4. 读取 `~/.minimax/auth/prod/<region>/mcode-public/auth.json` 的 OAuth 令牌,按需续期
|
|
139
|
+
5. 调用 `POST /matrix/api/v1/commerce/get_membership_info` 获取积分余额,通过插件自己的状态接口 `/plugins/dsh-connect-minimaxcode/status` 交给界面胶囊
|
|
125
140
|
|
|
126
141
|
### 与网关对接时的三个兼容处理
|
|
127
142
|
|
|
@@ -135,45 +150,83 @@ MiniMax 的这些模型**强制要求先推理再输出**,思考无法关闭
|
|
|
135
150
|
|
|
136
151
|
插件**不**包含任何硬编码的 Token、账号或接口密钥。
|
|
137
152
|
|
|
138
|
-
##
|
|
153
|
+
## 积分余额与用量
|
|
139
154
|
|
|
140
|
-
|
|
155
|
+
对话框输入框下方的胶囊会显示 MiniMax Code 的**剩余积分**;点开还能看到赠送/购买拆分、两次读取之间的消耗,以及本机累计的 token 与等效金额。
|
|
141
156
|
|
|
142
|
-
|
|
157
|
+
### 余额从哪里来
|
|
158
|
+
|
|
159
|
+
MiniMax Code 桌面 App 自己每天在调一个「会员信息」接口,响应里就带着客户端设置页显示的那三个数字:
|
|
143
160
|
|
|
144
161
|
```
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
POST https://agent.minimax.cn/matrix/api/v1/commerce/get_membership_info
|
|
163
|
+
Authorization: Bearer <桌面 App 的 mmoat_ OAuth 令牌>
|
|
164
|
+
Content-Type: application/json
|
|
165
|
+
|
|
166
|
+
{}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
"op_credit_summary": {
|
|
171
|
+
"total_remaining_amount": "11459.454", // 总剩余积分
|
|
172
|
+
"purchased_remaining_amount": "0", // 其中购买的
|
|
173
|
+
"free_remaining_amount": "11459.454" // 其中赠送的
|
|
149
174
|
}
|
|
150
175
|
```
|
|
151
176
|
|
|
152
|
-
|
|
177
|
+
插件读的就是 `op_credit_summary.total_remaining_amount`。**注意别取错字段**:同一个响应里的 `total_remains_credit` 与 `opcredit_balance` 是旧账,在已迁移到积分池的账号上恒为 `0`,取它们会显示「0 积分」。
|
|
178
|
+
|
|
179
|
+
顺带一提,这里有三个**单位不同**的「钱」,别混:
|
|
180
|
+
|
|
181
|
+
| 数值 | 含义 | 本插件是否显示 |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `op_credit_summary.total_remaining_amount` | Code 积分余额 | ✅ 就是它 |
|
|
184
|
+
| `token_plan_credit.remaining_credits` | 已购积分池 | ❌ |
|
|
185
|
+
| `query_balance.available_amount` | 开放平台钱包(元) | ❌ |
|
|
186
|
+
|
|
187
|
+
### 消耗怎么算
|
|
188
|
+
|
|
189
|
+
两路数据各管一件事:
|
|
190
|
+
|
|
191
|
+
- **真实积分消耗**:余额是个活值,两次读取的差值就是这段时间真正花掉的积分(客户端也会并发消耗,所以差值含其它客户端的开销)。
|
|
192
|
+
- **本机消耗**:每次请求的响应都自带 `usage`(`input_tokens` / `output_tokens` / `cache_creation_input_tokens` / `cache_read_input_tokens`),插件原样累计。折算金额时**只用这四个分量自己算**,不能用任何 `total_token` 汇总值——上游的两级汇总口径不同(差 1.94 倍,差额正是缓存读取部分)。单价取自 [MiniMax 官方价目表](https://platform.minimax.cn/docs/pricing/overview):
|
|
193
|
+
|
|
194
|
+
| 模型 | 输入 | 输出 | 缓存读取 |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| `MiniMax-M3.1-Flash` | 1.00 元/百万 tokens | 4.00 | 0.10 |
|
|
197
|
+
| `MiniMax-M3`(≤512k 输入) | 2.10 | 8.40 | 0.42 |
|
|
198
|
+
| `MiniMax-M3`(>512k 输入) | 4.20 | 16.80 | 0.84 |
|
|
153
199
|
|
|
154
|
-
|
|
155
|
-
|---|---|
|
|
156
|
-
| `GET /v1/api/user/info` + 模型网关令牌 | `401`,两类凭证互不通用 |
|
|
157
|
-
| `GET /v1/api/user/info` + 客户端 OAuth 令牌(`mmoat_`) | `200`,但响应只有昵称、头像、`vipInfo` 等账户资料,**不含任何额度字段** |
|
|
158
|
-
| 14 个候选额度路径(`user/credits`、`user/quota`、`workspace/list`、`credit/summary` 等) | 全部 `404` |
|
|
159
|
-
| `account.minimax.cn` 同一批路径 | 同样无额度字段 |
|
|
200
|
+
折算出的金额是**等效金额**,不是扣费:积分与金额之间 MiniMax 没有公开换算关系(套餐只公布了 1×/3×/12× 的相对倍数),所以插件不会把它说成「花了多少积分」。
|
|
160
201
|
|
|
161
|
-
|
|
202
|
+
### 关于续期
|
|
162
203
|
|
|
163
|
-
|
|
204
|
+
OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自己的令牌已过期时**才用 App 已存的 refresh token 续一次,并把轮换后的新令牌按客户端**原格式原子写回**同一个文件(`generation` 保持不变),因此桌面 App 的登录态不受影响。插件不会发起登录、不会登出、也不会调用任何付费/订阅类接口。
|
|
205
|
+
|
|
206
|
+
> 这一节的结论与 0.2.x 的「无法显示额度」相反。当时的调查按 `credits` / `quota` / `summary` 这类英文语义猜路径,真实接口用的是 `commerce` / `membership` 这套计费域词汇,因此全部落空;后来是从桌面 App 自己的运行日志(`%APPDATA%\MiniMax\logs\api-*.log`)里读出它每天真实调用的 URL,才定位到这条路径。
|
|
164
207
|
|
|
165
208
|
## 已知限制
|
|
166
209
|
|
|
167
210
|
- **仅支持国内版(`agent.minimax.cn`)**。国际版网关是否可用**未经验证**,请勿据此推断其他区域可用。
|
|
168
211
|
- **必须开启推理**。MiniMax 的全部模型都声明需要推理,关不掉。首字延迟因此高于非推理模型。
|
|
169
212
|
- **未登录时模型列表是内置兜底**。此时选择器中仍会显示 `M3`、`M3.1-Flash` 两个内置条目,但它们无法真正调用;登录后才会切换为上游实时目录。判断是否可用,请以实际调用结果为准。
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
213
|
+
- **积分只覆盖 MiniMax Code 的赠送/购买积分**,不包含开放平台钱包余额与已购积分池,插件也不会把金额折算成积分。
|
|
214
|
+
- **余额是账户级数值**,两次读取的差值包含 MiniMax Code 其它客户端的并发消耗;本机单独的消耗看胶囊里的 token 数与等效金额。
|
|
215
|
+
- **等效金额是按公开价目表折算的估值**,不是实际扣费;官方未公开 token 与积分的换算关系。
|
|
216
|
+
- **OAuth 令牌过期时积分胶囊会暂时失效**,需打开一次 MiniMax Code 桌面 App 让它自行续期(或等待插件续期成功)。这不影响模型对话。
|
|
217
|
+
- **不提供设置界面卡片**。插件不在 DSH 设置页注册卡片;界面只增加输入框下方的一枚胶囊。
|
|
218
|
+
- **上游接口非公开**,MiniMax 客户端更新后可能失效。余额接口失效时胶囊会显示「积分不可用」,不会影响模型对话。
|
|
174
219
|
|
|
175
220
|
## 更新记录
|
|
176
221
|
|
|
222
|
+
### 0.3.0(2026-10-19)
|
|
223
|
+
|
|
224
|
+
- **新增积分余额显示**:对话框输入框下方的胶囊显示 MiniMax Code 的剩余积分(赠送/购买拆分),点开可看两次读取之间的消耗、本机累计 token 与按官方价目表折算的等效金额。
|
|
225
|
+
- **新增用量计量**:每次请求的 `usage` 取自上游响应本身,累计到本机并折算金额。
|
|
226
|
+
- **新增账户接口定位**:找到桌面 App 每天真实调用的 `POST /matrix/api/v1/commerce/get_membership_info`,并**推翻了 0.2.x 「无法显示额度」的结论**——原因见「积分余额与用量」末尾。
|
|
227
|
+
- **令牌策略调整**:模型网关用 JWT、账户接口用桌面 App 的 OAuth 令牌;后者过期时插件会用 App 已存的 refresh token 续期,并按客户端原格式写回,不影响桌面 App 登录态。
|
|
228
|
+
- **首次带浏览器侧插件**:新增 `lib/client.js`(classic script),只注册一枚 composer dock 胶囊。**升级后需重启一次 DSH**。
|
|
229
|
+
|
|
177
230
|
### 0.2.3(2026-10-19)
|
|
178
231
|
|
|
179
232
|
- **同步上游模型变动**:MiniMax 下架了 `M2.7`、`M2.7-highspeed` 两个旧模型;此前以匿名形式灰度的测试模型(代号 "space bunny" / 太空兔)正式发布为 **`M3.1-Flash`**。当前上游实时目录为 `M3`、`M3.1-Flash`。
|
|
@@ -198,6 +251,8 @@ pnpm build # 构建
|
|
|
198
251
|
|
|
199
252
|
构建依赖 `@earendil-works/pi-ai`,需与宿主中 `dsh-llm-pi-ai` 解析出的版本一致(当前为 `0.85.x`),否则类型不兼容。
|
|
200
253
|
|
|
254
|
+
界面半边(`src/client.ts`)由 `tsconfig.client.json` 单独编译成 **classic script**(`lib/client.js`):宿主以 `<script>` 方式加载客户端 bundle,若产出 ESM 会直接导致 Web 启动失败(`Cannot use import statement outside a module`),所以这里刻意不用打包器、也不自带 React——React 来自宿主的浏览器模块表。
|
|
255
|
+
|
|
201
256
|
**`lib/` 是纳入版本管理的构建产物**,不是生成物。这是刻意为之:git 源安装拿到的是仓库原样内容,若不含 `lib/`,pnpm 会判定该包"需要构建"并触发 `prepare`,随即被构建脚本白名单拦下。改动源码后请重新构建并提交:
|
|
202
257
|
|
|
203
258
|
```bash
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The browser half: a credit pill in the composer's dock.
|
|
4
|
+
*
|
|
5
|
+
* The host loads a client bundle as a classic script and calls
|
|
6
|
+
* `window.__ModuleLoader__.load`, handing the factory a `require` that
|
|
7
|
+
* resolves against the browser's own module table. React therefore comes from
|
|
8
|
+
* the host — this file installs no React of its own, and ships no bundler
|
|
9
|
+
* output: it is a script, so it can be dropped into the page as-is.
|
|
10
|
+
*
|
|
11
|
+
* The pill shows the account's remaining credits and what this host's
|
|
12
|
+
* conversations have used, both read from the plugin's own status route. It
|
|
13
|
+
* degrades quietly: a failed read keeps the last value rather than blanking
|
|
14
|
+
* the dock.
|
|
15
|
+
*/
|
|
16
|
+
;
|
|
17
|
+
(function bootstrapMinimaxCodeClient() {
|
|
18
|
+
const BUNDLE_NAME = 'dsh-connect-minimaxcode';
|
|
19
|
+
const NAMESPACE = 'dsh-connect-minimaxcode';
|
|
20
|
+
const STATUS_PATH = '/plugins/dsh-connect-minimaxcode/status';
|
|
21
|
+
const POLL_INTERVAL_MS = 20000;
|
|
22
|
+
const zh = {
|
|
23
|
+
title: 'MiniMax Code 积分',
|
|
24
|
+
balance: '积分 {total}',
|
|
25
|
+
free: '赠送积分',
|
|
26
|
+
purchased: '购买积分',
|
|
27
|
+
consumed: '上次读取后消耗',
|
|
28
|
+
tokens: '本机累计 {count} tokens',
|
|
29
|
+
cost: '等效金额 ≈ ¥{amount}',
|
|
30
|
+
requests: '{count} 次请求',
|
|
31
|
+
updated: '更新于 {time}',
|
|
32
|
+
unavailable: '积分不可用',
|
|
33
|
+
expired: '登录态已过期',
|
|
34
|
+
signedOut: '未登录 MiniMax Code',
|
|
35
|
+
close: '收起',
|
|
36
|
+
};
|
|
37
|
+
const en = {
|
|
38
|
+
title: 'MiniMax Code credits',
|
|
39
|
+
balance: '{total} credits',
|
|
40
|
+
free: 'Free credits',
|
|
41
|
+
purchased: 'Purchased credits',
|
|
42
|
+
consumed: 'Spent since last read',
|
|
43
|
+
tokens: '{count} tokens used here',
|
|
44
|
+
cost: 'Equivalent ≈ ¥{amount}',
|
|
45
|
+
requests: '{count} requests',
|
|
46
|
+
updated: 'Updated {time}',
|
|
47
|
+
unavailable: 'Credits unavailable',
|
|
48
|
+
expired: 'Sign-in expired',
|
|
49
|
+
signedOut: 'MiniMax Code not signed in',
|
|
50
|
+
close: 'Hide',
|
|
51
|
+
};
|
|
52
|
+
const loader = globalThis.__ModuleLoader__;
|
|
53
|
+
if (loader === undefined)
|
|
54
|
+
return;
|
|
55
|
+
loader.load({
|
|
56
|
+
id: BUNDLE_NAME,
|
|
57
|
+
factory(require) {
|
|
58
|
+
const React = require('react');
|
|
59
|
+
const h = React.createElement;
|
|
60
|
+
/** Format a credit amount without pretending to more precision. */
|
|
61
|
+
function credits(value, digits) {
|
|
62
|
+
if (value === undefined || !Number.isFinite(value))
|
|
63
|
+
return '—';
|
|
64
|
+
return value.toLocaleString(undefined, { maximumFractionDigits: digits, minimumFractionDigits: 0 });
|
|
65
|
+
}
|
|
66
|
+
/** Format a token count compactly. */
|
|
67
|
+
function tokens(value) {
|
|
68
|
+
if (!Number.isFinite(value))
|
|
69
|
+
return '—';
|
|
70
|
+
if (value < 1000)
|
|
71
|
+
return String(value);
|
|
72
|
+
if (value < 1000000)
|
|
73
|
+
return `${(value / 1000).toFixed(1)}k`;
|
|
74
|
+
return `${(value / 1000000).toFixed(2)}M`;
|
|
75
|
+
}
|
|
76
|
+
/** Format a small amount of money. */
|
|
77
|
+
function money(value) {
|
|
78
|
+
if (value === undefined || !Number.isFinite(value))
|
|
79
|
+
return '—';
|
|
80
|
+
if (value === 0)
|
|
81
|
+
return '0';
|
|
82
|
+
if (value < 0.01)
|
|
83
|
+
return value.toFixed(4);
|
|
84
|
+
return value.toFixed(2);
|
|
85
|
+
}
|
|
86
|
+
/** The pill, plus the detail panel it opens. */
|
|
87
|
+
function CreditPill(props) {
|
|
88
|
+
const [status, setStatus] = React.useState(undefined);
|
|
89
|
+
const [open, setOpen] = React.useState(false);
|
|
90
|
+
// `t` comes from the host's locale service when the slot declares the
|
|
91
|
+
// namespace; the built-in copy is the fallback for a host that does not.
|
|
92
|
+
const t = props.t ?? ((key, params) => {
|
|
93
|
+
const template = zh[key] ?? key;
|
|
94
|
+
return template.replace(/\{(\w+)\}/g, (_match, name) => String(params?.[name] ?? ''));
|
|
95
|
+
});
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
let cancelled = false;
|
|
98
|
+
const load = async () => {
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(STATUS_PATH, { headers: { accept: 'application/json' } });
|
|
101
|
+
if (!response.ok)
|
|
102
|
+
return;
|
|
103
|
+
const document = await response.json();
|
|
104
|
+
if (!cancelled)
|
|
105
|
+
setStatus(document);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Keep the last value: a failed poll is not worth a blank pill.
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
void load();
|
|
112
|
+
const timer = setInterval(() => { void load(); }, POLL_INTERVAL_MS);
|
|
113
|
+
return () => {
|
|
114
|
+
cancelled = true;
|
|
115
|
+
clearInterval(timer);
|
|
116
|
+
};
|
|
117
|
+
}, []);
|
|
118
|
+
const credits_ = status?.credits;
|
|
119
|
+
const usage = status?.usage;
|
|
120
|
+
const available = credits_?.state === 'ok' && credits_.total !== undefined;
|
|
121
|
+
const label = available
|
|
122
|
+
? t('balance', { total: credits(credits_.total, 2) })
|
|
123
|
+
: t(credits_?.state === 'expired' ? 'expired' : credits_?.state === 'signed-out' ? 'signedOut' : 'unavailable');
|
|
124
|
+
const detail = available
|
|
125
|
+
? h('div', { style: detailStyle, 'data-minimax-credits-detail': true }, h('div', { style: detailRowStyle }, h('span', null, t('free')), h('span', null, credits(credits_.free, 3))), h('div', { style: detailRowStyle }, h('span', null, t('purchased')), h('span', null, credits(credits_.purchased, 3))), credits_.consumed !== undefined && credits_.consumed > 0
|
|
126
|
+
? h('div', { style: detailRowStyle }, h('span', null, t('consumed')), h('span', null, credits(credits_.consumed, 3)))
|
|
127
|
+
: null, usage !== undefined
|
|
128
|
+
? h('div', { style: detailRowStyle }, h('span', null, t('tokens', { count: tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens) })), h('span', null, t('cost', { amount: money(usage.estimatedCny) })))
|
|
129
|
+
: null, usage !== undefined && usage.requests > 0
|
|
130
|
+
? h('div', { style: detailRowStyle }, h('span', null, t('requests', { count: usage.requests })), h('span', null, usage.lastModel ?? ''))
|
|
131
|
+
: null, credits_.updatedAtMs !== undefined
|
|
132
|
+
? h('div', { style: detailRowStyle }, h('span', null, t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() })), h('span', null, ''))
|
|
133
|
+
: null)
|
|
134
|
+
: null;
|
|
135
|
+
return h('div', { style: wrapStyle, 'data-minimax-credits': true }, h('button', {
|
|
136
|
+
type: 'button',
|
|
137
|
+
style: buttonStyle,
|
|
138
|
+
title: t('title'),
|
|
139
|
+
onClick: () => setOpen(!open),
|
|
140
|
+
}, label), open ? detail : null);
|
|
141
|
+
}
|
|
142
|
+
const wrapStyle = {
|
|
143
|
+
position: 'relative',
|
|
144
|
+
display: 'inline-flex',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
};
|
|
147
|
+
const buttonStyle = {
|
|
148
|
+
appearance: 'none',
|
|
149
|
+
background: 'transparent',
|
|
150
|
+
border: '1px solid var(--dsh-composer-dock-inset, currentColor)',
|
|
151
|
+
borderRadius: '999px',
|
|
152
|
+
color: 'inherit',
|
|
153
|
+
cursor: 'pointer',
|
|
154
|
+
font: 'inherit',
|
|
155
|
+
fontSize: '12px',
|
|
156
|
+
lineHeight: 1.4,
|
|
157
|
+
opacity: 0.8,
|
|
158
|
+
padding: '1px 8px',
|
|
159
|
+
};
|
|
160
|
+
const detailStyle = {
|
|
161
|
+
position: 'absolute',
|
|
162
|
+
bottom: 'calc(100% + 6px)',
|
|
163
|
+
left: 0,
|
|
164
|
+
zIndex: 10,
|
|
165
|
+
minWidth: '220px',
|
|
166
|
+
borderRadius: '8px',
|
|
167
|
+
background: 'var(--dsh-panel-background, Canvas)',
|
|
168
|
+
color: 'var(--dsh-panel-foreground, CanvasText)',
|
|
169
|
+
border: '1px solid var(--dsh-panel-border, currentColor)',
|
|
170
|
+
boxShadow: '0 6px 20px rgba(0,0,0,0.18)',
|
|
171
|
+
padding: '8px 10px',
|
|
172
|
+
fontSize: '12px',
|
|
173
|
+
lineHeight: 1.6,
|
|
174
|
+
textAlign: 'left',
|
|
175
|
+
};
|
|
176
|
+
const detailRowStyle = {
|
|
177
|
+
display: 'flex',
|
|
178
|
+
justifyContent: 'space-between',
|
|
179
|
+
gap: '16px',
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
inject: ['slots', 'locale'],
|
|
183
|
+
apply(ctx) {
|
|
184
|
+
const context = ctx;
|
|
185
|
+
// Visible text goes through the host's locale service.
|
|
186
|
+
context.effect(() => context.locale.register(NAMESPACE, { zh, en }), `${BUNDLE_NAME}: locale`);
|
|
187
|
+
// The composer dock is the strip under the input box, which is where
|
|
188
|
+
// the host itself renders its activity and usage pills.
|
|
189
|
+
context.slots.inject('conversation.composer.dock', () => context.slots.register({
|
|
190
|
+
name: 'conversation.composer.dock',
|
|
191
|
+
id: 'minimax-credits',
|
|
192
|
+
order: 2,
|
|
193
|
+
locale: NAMESPACE,
|
|
194
|
+
}, CreditPill));
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
})();
|