dsh-grok-subscription 1.0.9 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -4
- package/README.md +6 -4
- package/lib/client.js +201 -17
- package/lib/index.js +691 -63
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -25,13 +25,13 @@ Models, reasoning effort, and weekly quota all stay inside DSH.
|
|
|
25
25
|
|
|
26
26
|
## Three-step start
|
|
27
27
|
|
|
28
|
-
1. **Install the plugin.** Run the command below; to select a candidate version, enter the full `package@version`, for example `dsh-grok-subscription@1.0.
|
|
28
|
+
1. **Install the plugin.** Run the command below; to select a candidate version, enter the full `package@version`, for example `dsh-grok-subscription@1.0.10`.
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
31
|
dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login
|
|
34
|
+
2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login**. The plugin runs the official `grok login`, hands the link the CLI prints to your browser, and shows the one-time code plus a clickable link in the panel; the session syncs itself once you authorize. If you already ran `grok login` yourself, just click **Pull from Grok CLI** — nothing has to be pasted.
|
|
35
35
|
3. **Start using it.** Pick a model such as Grok 4.7 in the model picker. The round badge next to the picker shows weekly remaining quota, and the model menu offers reasoning effort levels.
|
|
36
36
|
|
|
37
37
|
Restart `dsh web` after installing or upgrading, otherwise the old `lib/` keeps running.
|
|
@@ -91,7 +91,7 @@ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
|
91
91
|
You can also install the version published on npm:
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
|
-
dsh plugin --profile web add dsh-grok-subscription@1.0.
|
|
94
|
+
dsh plugin --profile web add dsh-grok-subscription@1.0.10
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
DSH handles target selection, the profile lock, dependency resolution, and bundle activation.
|
|
@@ -146,7 +146,8 @@ Restart DSH manually afterwards, then:
|
|
|
146
146
|
- Settings can show the used and remaining percentages the backend reports, and says so plainly instead of guessing when the read fails;
|
|
147
147
|
- Supports CLI login, device-code login, pull from the Grok CLI, and logout;
|
|
148
148
|
- Renews the access token automatically: near the end of its 6-hour life, or on a 401, it runs the official `grok` CLI and retries once, so a session never lapses mid-use; the chat model list refreshes whenever sign-in state changes;
|
|
149
|
-
- When subscription routing is unavailable it reports an error instead of silently switching to another paid route
|
|
149
|
+
- When subscription routing is unavailable it reports an error instead of silently switching to another paid route;
|
|
150
|
+
- Prefix caching follows xAI's automatic prompt-cache semantics: one DSH session keeps a stable `prompt_cache_key`, tool definitions stay in a stable order, and the fallback adapter preserves and replays `reasoning.encrypted_content` unchanged. Server-side eviction can still miss, and the plugin never invents a hit rate.
|
|
150
151
|
|
|
151
152
|
### Composer quota
|
|
152
153
|
|
|
@@ -208,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
|
|
|
208
209
|
|
|
209
210
|
- **`dsh` is not recognized**: the official npm route never creates a global `dsh` command — use the full `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` command above;
|
|
210
211
|
- **The model list is empty**: nothing is exposed while signed out. Finish signing in, then click **Pull from Grok CLI**;
|
|
212
|
+
- **Sign-in opens no browser / chat fails with `fetch failed` / usage says "Billing request timed out"**: all three have one cause — `accounts.x.ai` and `cli-chat-proxy.grok.com` need a tunnel on some networks. Add `GROK_PROXY=http://127.0.0.1:10808` to `~/.dsh/.env`: it applies to **this plugin's Grok requests** (catalog, usage, chat) and to the **`grok` CLI** it spawns (sign-in, renewal), while DSH and every other provider stay direct — an `http_proxy` line there would route them all through the tunnel and break them whenever it is down. Restart the client afterwards; the sign-in link is handed to your browser and the panel also shows the one-time code and a clickable link. Standard proxy variables already present in the environment are inherited by the CLI too;
|
|
211
213
|
- **Nothing changed after upgrading**: the plugin keeps running the old `lib/` — reinstall the plugin, restart `dsh web`, and hard-refresh (Ctrl+Shift+R);
|
|
212
214
|
- **It reports `auth.json` permissions**: apply the `chmod 600` above; the plugin refuses symlinks and group- or other-readable files;
|
|
213
215
|
- **"No reply" after sending a message**: upgrade to `1.0.1` or later. `1.0.0` had a defect where tool-calling turns aborted with a non-serializable stream chunk, leaving no reply in the UI at all.
|
package/README.md
CHANGED
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
|
|
26
26
|
## 三步开始
|
|
27
27
|
|
|
28
|
-
1. **安装插件**:在终端运行下面的命令;指定候选版本时填写完整的 `包名@版本`,例如 `dsh-grok-subscription@1.0.
|
|
28
|
+
1. **安装插件**:在终端运行下面的命令;指定候选版本时填写完整的 `包名@版本`,例如 `dsh-grok-subscription@1.0.10`。
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
31
|
dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或
|
|
34
|
+
2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或 **设备码登录**。插件会运行官方 `grok login`,把 CLI 打印的登录链接交给浏览器,并在面板里显示一次性验证码与可点的链接;授权完成后自动同步。已经自己跑过 `grok login` 的话,直接点 **从 Grok CLI 拉取** 即可,不需要粘贴任何 token。
|
|
35
35
|
3. **开始使用**:在模型选择器中选择 Grok 4.7 等模型。输入框旁的圆形徽章显示每周剩余额度,模型菜单里有推理档位。
|
|
36
36
|
|
|
37
37
|
安装或升级后需要重启 `dsh web`,否则会继续运行旧的 `lib/`。
|
|
@@ -91,7 +91,7 @@ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
|
91
91
|
也可以按 npm 上的已发布版本安装:
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
|
-
dsh plugin --profile web add dsh-grok-subscription@1.0.
|
|
94
|
+
dsh plugin --profile web add dsh-grok-subscription@1.0.10
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
目标选择、profile 锁、依赖解析和 bundle 激活均由 DSH 负责。
|
|
@@ -146,7 +146,8 @@ dsh --profile web --dump-config
|
|
|
146
146
|
- 设置页可查看服务端返回的用量与剩余百分比,失败时明确提示而不是显示猜测值;
|
|
147
147
|
- 支持 CLI 登录、设备码登录、从 Grok CLI 拉取和登出;登录状态变化后 Chat 模型列表自动刷新;
|
|
148
148
|
- access token 到期自动续期:临近 6 小时有效期或收到 401 时,调用官方 `grok` CLI 刷新会话并重试一次,不会中途要求手动重新登录;
|
|
149
|
-
-
|
|
149
|
+
- 订阅路由不可用时明确报错,不会静默切换到其他付费路由;
|
|
150
|
+
- 前缀缓存按 xAI 的自动缓存语义优化:同一 DSH 会话使用稳定的 `prompt_cache_key`,工具定义保持稳定顺序,回退适配器会保存并原样回放 `reasoning.encrypted_content`。缓存仍可能因服务端逐出而失效,插件不虚构命中率。
|
|
150
151
|
|
|
151
152
|
### 输入框额度
|
|
152
153
|
|
|
@@ -208,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
|
|
|
208
209
|
|
|
209
210
|
- **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` 命令;
|
|
210
211
|
- **模型列表是空的**:未登录时插件不暴露任何模型。先完成登录,再点 **从 Grok CLI 拉取**;
|
|
212
|
+
- **登录不弹浏览器 / 聊天报 `fetch failed` / 用量「Billing request timed out」**:这三件事都指向同一个原因——`accounts.x.ai` 与 `cli-chat-proxy.grok.com` 在部分网络里必须走隧道。在 `~/.dsh/.env` 写一行 `GROK_PROXY=http://127.0.0.1:10808` 即可:它只作用于**本插件的 Grok 请求**(模型目录、用量、聊天)和它启动的 **`grok` CLI**(登录、续期),DSH 自身与其他 provider 仍走直连——把 `http_proxy` 写在这里会让所有 provider 一起走隧道,隧道一停就全连不上。写好后重启客户端;登录链接会交给系统浏览器打开,面板里同时显示一次性验证码与可点链接。环境里已有的标准代理变量同样会被 CLI 继承;
|
|
211
213
|
- **升级后界面没变化**:插件会继续运行旧的 `lib/`,请重新安装插件并重启 `dsh web`,然后硬刷新(Ctrl+Shift+R);
|
|
212
214
|
- **提示 `auth.json` 权限不正确**:按上面的 `chmod 600` 处理;插件拒绝读取符号链接或组/其他用户可读的文件;
|
|
213
215
|
- **出现「没有回复」**:请升级到 `1.0.1` 或更高版本。`1.0.0` 存在一个缺陷:工具调用轮次会因流片段无法无损序列化而整轮中止,界面上完全没有回复。
|
package/lib/client.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(client_exports, {
|
|
|
26
26
|
RPC_CALL_TIMEOUT_MS: () => RPC_CALL_TIMEOUT_MS,
|
|
27
27
|
SectionBoundary: () => SectionBoundary,
|
|
28
28
|
UsagePanel: () => UsagePanel,
|
|
29
|
+
VersionCard: () => VersionCard,
|
|
29
30
|
apply: () => apply,
|
|
30
31
|
callRpc: () => callRpc,
|
|
31
32
|
inject: () => inject,
|
|
@@ -38,7 +39,7 @@ var import_react2 = require("react");
|
|
|
38
39
|
var zh = {
|
|
39
40
|
nav: "Grok \u8BA2\u9605",
|
|
40
41
|
title: "Grok \u8BA2\u9605",
|
|
41
|
-
subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.
|
|
42
|
+
subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.18",
|
|
42
43
|
signedIn: "\u5DF2\u767B\u5F55",
|
|
43
44
|
signedOut: "\u672A\u767B\u5F55",
|
|
44
45
|
account: "\u8D26\u6237",
|
|
@@ -53,8 +54,12 @@ var zh = {
|
|
|
53
54
|
loginDevice: "\u8BBE\u5907\u7801\u767B\u5F55",
|
|
54
55
|
pull: "\u4ECE Grok CLI \u62C9\u53D6",
|
|
55
56
|
logout: "\u9000\u51FA\u767B\u5F55",
|
|
56
|
-
loginHint: "\
|
|
57
|
-
deviceHint: "\u8BBE\u5907\u7801\
|
|
57
|
+
loginHint: "\u70B9\u51FB\u540E\u5728\u672C\u673A\u8FD0\u884C grok login\uFF0C\u5E76\u628A CLI \u6253\u5370\u7684\u767B\u5F55\u94FE\u63A5\u4EA4\u7ED9\u6D4F\u89C8\u5668\uFF1B\u6388\u6743\u5B8C\u6210\u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u540C\u6B65\u3002",
|
|
58
|
+
deviceHint: "\u8BBE\u5907\u7801\u767B\u5F55\u4F1A\u8BA9 CLI \u6253\u5370\u4E00\u6B21\u6027\u9A8C\u8BC1\u7801\u548C\u94FE\u63A5\u3002\u82E5\u6D4F\u89C8\u5668\u6CA1\u6709\u81EA\u52A8\u6253\u5F00\uFF0C\u70B9\u63D0\u793A\u91CC\u7684\u201C\u6253\u5F00\u767B\u5F55\u9875\u9762\u201D\u5E76\u786E\u8BA4\u9A8C\u8BC1\u7801\u3002",
|
|
59
|
+
loginWaiting: "\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u2026",
|
|
60
|
+
loginOpenBrowser: "\u6253\u5F00\u767B\u5F55\u9875\u9762",
|
|
61
|
+
loginNoUrl: "\u6CA1\u62FF\u5230\u767B\u5F55\u94FE\u63A5\uFF1A\u8BF7\u786E\u8BA4 grok CLI \u80FD\u8BBF\u95EE x.ai\uFF08\u684C\u9762\u7248\u901A\u5E38\u9700\u8981\u4EE3\u7406\uFF09\uFF0C\u7136\u540E\u91CD\u8BD5\u3002",
|
|
62
|
+
loginTimeout: "\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002",
|
|
58
63
|
pullHint: "\u5B89\u5168\u8BFB\u53D6 ~/.grok/auth.json\uFF08\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\u4E0E\u7EC4/\u5176\u4ED6\u4EBA\u53EF\u8BFB\uFF09\uFF0C\u53EA\u628A\u77ED\u671F access token \u5199\u5165 DSH\u3002",
|
|
59
64
|
loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
|
|
60
65
|
refreshCatalog: "\u5237\u65B0\u6A21\u578B\u76EE\u5F55",
|
|
@@ -90,12 +95,31 @@ var zh = {
|
|
|
90
95
|
composerQuotaDetails: "\u6BCF\u5468\u989D\u5EA6",
|
|
91
96
|
composerQuotaRemaining: "\u6BCF\u5468\u989D\u5EA6 \u5269\u4F59 {remaining}%",
|
|
92
97
|
composerQuotaResets: "\u91CD\u7F6E\u4E8E {reset}",
|
|
93
|
-
composerQuotaResetUnknown: "\u91CD\u7F6E\u65F6\u95F4\u672A\u77E5"
|
|
98
|
+
composerQuotaResetUnknown: "\u91CD\u7F6E\u65F6\u95F4\u672A\u77E5",
|
|
99
|
+
versionTitle: "\u63D2\u4EF6\u7248\u672C",
|
|
100
|
+
versionCurrent: "\u5F53\u524D\u7248\u672C",
|
|
101
|
+
versionLatest: "\u6700\u65B0\u7248\u672C",
|
|
102
|
+
versionCheck: "\u68C0\u67E5\u66F4\u65B0",
|
|
103
|
+
versionChecking: "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
|
|
104
|
+
versionFailed: "\u65E0\u6CD5\u68C0\u67E5\u6700\u65B0\u7248\u672C\u3002",
|
|
105
|
+
versionUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
|
|
106
|
+
versionAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{version}\u3002",
|
|
107
|
+
versionLinked: "\u5F53\u524D\u4E3A\u672C\u5730\u5F00\u53D1\u5B89\u88C5\uFF0C\u8BF7\u5728\u63D2\u4EF6\u4ED3\u5E93\u62C9\u53D6\u6700\u65B0\u4EE3\u7801\u5E76\u91CD\u65B0\u6784\u5EFA\u3002",
|
|
108
|
+
versionManual: "\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C dsh plugin --profile web add dsh-grok-subscription@latest \u5B8C\u6210\u66F4\u65B0\u3002",
|
|
109
|
+
versionUpdate: "\u66F4\u65B0\u63D2\u4EF6",
|
|
110
|
+
versionUpdating: "\u6B63\u5728\u66F4\u65B0\u2026",
|
|
111
|
+
versionUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u6216\u5728\u7EC8\u7AEF\u624B\u52A8\u66F4\u65B0\u3002",
|
|
112
|
+
versionUpdated: "\u5DF2\u66F4\u65B0\u5230 v{version}\u3002",
|
|
113
|
+
versionUpdatedHint: "\u65B0\u7248\u672C\u9700\u8981\u91CD\u542F DSH \u670D\u52A1\u624D\u80FD\u5B8C\u5168\u751F\u6548\uFF1B\u4EC5\u5237\u65B0\u754C\u9762\u4E0D\u4F1A\u66F4\u65B0\u5BBF\u4E3B\u8FDB\u7A0B\u4E2D\u5DF2\u52A0\u8F7D\u7684\u63D2\u4EF6\u3002",
|
|
114
|
+
versionRestart: "\u91CD\u542F DSH \u670D\u52A1",
|
|
115
|
+
versionRestartHint: "\u63D2\u4EF6\u65E0\u6CD5\u5B89\u5168\u5730\u91CD\u542F\u5BBF\u4E3B\u8FDB\u7A0B\uFF1A\u8BF7\u5728\u8FD0\u884C DSH \u7684\u7EC8\u7AEF\u6309 Ctrl+C \u7ED3\u675F\u8FDB\u7A0B\uFF0C\u518D\u91CD\u65B0\u8FD0\u884C dsh web\u3002",
|
|
116
|
+
versionRefresh: "\u5237\u65B0\u754C\u9762",
|
|
117
|
+
versionLater: "\u7A0D\u540E"
|
|
94
118
|
};
|
|
95
119
|
var en = {
|
|
96
120
|
nav: "Grok Subscription",
|
|
97
121
|
title: "Grok Subscription",
|
|
98
|
-
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.
|
|
122
|
+
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.18",
|
|
99
123
|
signedIn: "Signed in",
|
|
100
124
|
signedOut: "Signed out",
|
|
101
125
|
account: "Account",
|
|
@@ -110,8 +134,12 @@ var en = {
|
|
|
110
134
|
loginDevice: "Device-code login",
|
|
111
135
|
pull: "Pull from Grok CLI",
|
|
112
136
|
logout: "Log out",
|
|
113
|
-
loginHint: "Runs grok login
|
|
114
|
-
deviceHint: "
|
|
137
|
+
loginHint: "Runs grok login on this machine and hands the link the CLI prints to your browser; the panel syncs itself once you authorize.",
|
|
138
|
+
deviceHint: "Device-code login makes the CLI print a one-time code and link. If no browser opened, use \u201COpen the sign-in page\u201D in the status line and confirm the code.",
|
|
139
|
+
loginWaiting: "Waiting for browser authorization\u2026",
|
|
140
|
+
loginOpenBrowser: "Open the sign-in page",
|
|
141
|
+
loginNoUrl: "No sign-in link was returned: make sure the grok CLI can reach x.ai (the desktop app usually needs a proxy), then retry.",
|
|
142
|
+
loginTimeout: "Timed out waiting for authorization; click sign-in again.",
|
|
115
143
|
pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files) and stores only the short-lived access token in DSH.",
|
|
116
144
|
loginHelp: "How signing in works",
|
|
117
145
|
refreshCatalog: "Refresh model catalog",
|
|
@@ -147,7 +175,26 @@ var en = {
|
|
|
147
175
|
composerQuotaDetails: "Weekly quota",
|
|
148
176
|
composerQuotaRemaining: "Weekly quota \xB7 {remaining}% left",
|
|
149
177
|
composerQuotaResets: "Resets {reset}",
|
|
150
|
-
composerQuotaResetUnknown: "Reset time unknown"
|
|
178
|
+
composerQuotaResetUnknown: "Reset time unknown",
|
|
179
|
+
versionTitle: "Plugin version",
|
|
180
|
+
versionCurrent: "Current version",
|
|
181
|
+
versionLatest: "Latest version",
|
|
182
|
+
versionCheck: "Check for updates",
|
|
183
|
+
versionChecking: "Checking for updates\u2026",
|
|
184
|
+
versionFailed: "Could not check the latest version.",
|
|
185
|
+
versionUpToDate: "You are on the latest version.",
|
|
186
|
+
versionAvailable: "A new version v{version} is available.",
|
|
187
|
+
versionLinked: "Installed from a local checkout; pull and rebuild the plugin repository to update.",
|
|
188
|
+
versionManual: "Run dsh plugin --profile web add dsh-grok-subscription@latest in a terminal to update.",
|
|
189
|
+
versionUpdate: "Update plugin",
|
|
190
|
+
versionUpdating: "Updating\u2026",
|
|
191
|
+
versionUpdateFailed: "The update failed. Try again or update manually in a terminal.",
|
|
192
|
+
versionUpdated: "Updated to v{version}.",
|
|
193
|
+
versionUpdatedHint: "The new version takes full effect after a DSH restart; refreshing the page alone does not reload the plugin inside the host process.",
|
|
194
|
+
versionRestart: "Restart DSH",
|
|
195
|
+
versionRestartHint: "The plugin cannot safely restart its host process: press Ctrl+C in the terminal running DSH, then run dsh web again.",
|
|
196
|
+
versionRefresh: "Refresh page",
|
|
197
|
+
versionLater: "Later"
|
|
151
198
|
};
|
|
152
199
|
|
|
153
200
|
// src/rpc-contract.js
|
|
@@ -160,7 +207,9 @@ var RPC_ENDPOINTS = Object.freeze([
|
|
|
160
207
|
"logout",
|
|
161
208
|
"catalog/refresh",
|
|
162
209
|
"usage",
|
|
163
|
-
"usage/refresh"
|
|
210
|
+
"usage/refresh",
|
|
211
|
+
"plugin/version",
|
|
212
|
+
"plugin/update"
|
|
164
213
|
]);
|
|
165
214
|
function createRpcClient(transport) {
|
|
166
215
|
return Object.freeze({
|
|
@@ -599,12 +648,14 @@ var inject = [
|
|
|
599
648
|
"slots",
|
|
600
649
|
"locale",
|
|
601
650
|
"connection",
|
|
602
|
-
"settingsScope",
|
|
603
651
|
"modelDirectories",
|
|
604
652
|
"sessions",
|
|
605
653
|
"remote"
|
|
606
654
|
];
|
|
607
655
|
var RPC_CALL_TIMEOUT_MS = 12e3;
|
|
656
|
+
var LOGIN_CALL_TIMEOUT_MS = 3e4;
|
|
657
|
+
var LOGIN_POLL_INTERVAL_MS = 3e3;
|
|
658
|
+
var LOGIN_POLL_TIMEOUT_MS = 10 * 6e4;
|
|
608
659
|
function pickCopy(locale) {
|
|
609
660
|
const language = typeof locale === "string" ? locale : locale?.language ?? locale?.lang;
|
|
610
661
|
const resolved = language ?? (typeof navigator !== "undefined" ? navigator.language : "zh-CN");
|
|
@@ -620,7 +671,7 @@ function formatPercent(value) {
|
|
|
620
671
|
const rounded = Math.round(value * 10) / 10;
|
|
621
672
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
622
673
|
}
|
|
623
|
-
async function callRpc(rpc, endpoint, payload = {}) {
|
|
674
|
+
async function callRpc(rpc, endpoint, payload = {}, timeoutMs = RPC_CALL_TIMEOUT_MS) {
|
|
624
675
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
625
676
|
let timer;
|
|
626
677
|
try {
|
|
@@ -631,8 +682,8 @@ async function callRpc(rpc, endpoint, payload = {}) {
|
|
|
631
682
|
controller?.abort();
|
|
632
683
|
} catch {
|
|
633
684
|
}
|
|
634
|
-
reject(new Error(`Request timed out after ${
|
|
635
|
-
},
|
|
685
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
686
|
+
}, timeoutMs);
|
|
636
687
|
});
|
|
637
688
|
return unwrap(await Promise.race([call, timeout]));
|
|
638
689
|
} finally {
|
|
@@ -687,6 +738,90 @@ function DiagnosticsRows({ diagnostics, t }) {
|
|
|
687
738
|
] })
|
|
688
739
|
] });
|
|
689
740
|
}
|
|
741
|
+
function fillTemplate2(template, values) {
|
|
742
|
+
return Object.entries(values).reduce(
|
|
743
|
+
(text, [key, value]) => text.replaceAll(`{${key}}`, value),
|
|
744
|
+
template
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
function VersionCard({ call, t }) {
|
|
748
|
+
const [info, setInfo] = (0, import_react2.useState)();
|
|
749
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
750
|
+
const [error, setError] = (0, import_react2.useState)(false);
|
|
751
|
+
const [updating, setUpdating] = (0, import_react2.useState)(false);
|
|
752
|
+
const [updateFailed, setUpdateFailed] = (0, import_react2.useState)(false);
|
|
753
|
+
const [updated, setUpdated] = (0, import_react2.useState)();
|
|
754
|
+
const [restartHint, setRestartHint] = (0, import_react2.useState)(false);
|
|
755
|
+
const generation = (0, import_react2.useRef)(0);
|
|
756
|
+
const load = (force) => {
|
|
757
|
+
const current = ++generation.current;
|
|
758
|
+
setLoading(true);
|
|
759
|
+
setError(false);
|
|
760
|
+
void call("plugin/version", { force }).then((value) => {
|
|
761
|
+
if (generation.current === current) setInfo(value);
|
|
762
|
+
}).catch(() => {
|
|
763
|
+
if (generation.current === current) setError(true);
|
|
764
|
+
}).finally(() => {
|
|
765
|
+
if (generation.current === current) setLoading(false);
|
|
766
|
+
});
|
|
767
|
+
};
|
|
768
|
+
(0, import_react2.useEffect)(() => {
|
|
769
|
+
load(false);
|
|
770
|
+
return () => {
|
|
771
|
+
generation.current += 1;
|
|
772
|
+
};
|
|
773
|
+
}, []);
|
|
774
|
+
const update = () => {
|
|
775
|
+
setUpdating(true);
|
|
776
|
+
setUpdateFailed(false);
|
|
777
|
+
void call("plugin/update").then((value) => {
|
|
778
|
+
setUpdated(value);
|
|
779
|
+
setRestartHint(false);
|
|
780
|
+
}).catch(() => setUpdateFailed(true)).finally(() => setUpdating(false));
|
|
781
|
+
};
|
|
782
|
+
const installKind = info?.install?.kind;
|
|
783
|
+
const showStatus = info !== void 0 && updated === void 0;
|
|
784
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsCard", children: [
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsCardHead", children: [
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("versionTitle") }),
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsSpacer" }),
|
|
788
|
+
updated === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: loading || updating, onClick: () => load(true), children: loading ? t("versionChecking") : t("versionCheck") }) : null
|
|
789
|
+
] }),
|
|
790
|
+
loading && info === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--busy", role: "status", children: t("versionChecking") }) : null,
|
|
791
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--error", role: "alert", children: t("versionFailed") }) : null,
|
|
792
|
+
info !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRows", children: [
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRow", children: [
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("versionCurrent") }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gsRowValue", children: [
|
|
796
|
+
"v",
|
|
797
|
+
info.current
|
|
798
|
+
] })
|
|
799
|
+
] }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRow", children: [
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("versionLatest") }),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gsRowValue", children: [
|
|
803
|
+
"v",
|
|
804
|
+
info.latest
|
|
805
|
+
] })
|
|
806
|
+
] })
|
|
807
|
+
] }) : null,
|
|
808
|
+
showStatus ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", role: "status", children: info.updateAvailable ? fillTemplate2(t("versionAvailable"), { version: info.latest }) : t("versionUpToDate") }) : null,
|
|
809
|
+
showStatus && info.updateAvailable && installKind === "npm" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gsActions", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: updating, onClick: update, children: updating ? t("versionUpdating") : t("versionUpdate") }) }) : null,
|
|
810
|
+
showStatus && installKind === "link" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionLinked") }) : null,
|
|
811
|
+
showStatus && info.updateAvailable && installKind === "unknown" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionManual") }) : null,
|
|
812
|
+
updateFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--error", role: "alert", children: t("versionUpdateFailed") }) : null,
|
|
813
|
+
updated !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "status", children: [
|
|
814
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: fillTemplate2(t("versionUpdated"), { version: updated.version }) }),
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionUpdatedHint") }),
|
|
816
|
+
restartHint ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionRestartHint") }) : null,
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", onClick: () => setRestartHint(true), children: t("versionRestart") }),
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", onClick: () => window.location.reload(), children: t("versionRefresh") }),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", onClick: () => setUpdated(void 0), children: t("versionLater") })
|
|
821
|
+
] })
|
|
822
|
+
] }) : null
|
|
823
|
+
] });
|
|
824
|
+
}
|
|
690
825
|
function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
|
|
691
826
|
const ok = usage?.status === "ok";
|
|
692
827
|
const usedNumber = ok && Number.isFinite(usage.usedPercent) ? usage.usedPercent : void 0;
|
|
@@ -751,6 +886,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
751
886
|
const [error, setError] = (0, import_react2.useState)("");
|
|
752
887
|
const [notice, setNotice] = (0, import_react2.useState)("");
|
|
753
888
|
const [status, setStatus] = (0, import_react2.useState)(void 0);
|
|
889
|
+
const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
|
|
754
890
|
const initialUsageKick = (0, import_react2.useRef)(false);
|
|
755
891
|
const applyPartial = (value) => {
|
|
756
892
|
if (value?.account || value?.catalog || value?.usage) {
|
|
@@ -789,8 +925,29 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
789
925
|
}).catch(() => {
|
|
790
926
|
});
|
|
791
927
|
};
|
|
928
|
+
(0, import_react2.useEffect)(() => {
|
|
929
|
+
if (!loginPending) return void 0;
|
|
930
|
+
const startedAt = Date.now();
|
|
931
|
+
const timer = setInterval(() => {
|
|
932
|
+
if (Date.now() - startedAt > LOGIN_POLL_TIMEOUT_MS) {
|
|
933
|
+
setLoginPending(void 0);
|
|
934
|
+
setError(t("loginTimeout"));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
void callRpc(rpc, "status", {}).then((value) => {
|
|
938
|
+
if (value?.account || value?.catalog || value?.usage) applyPartial(value);
|
|
939
|
+
if (value?.account?.signedIn !== true) return;
|
|
940
|
+
setLoginPending(void 0);
|
|
941
|
+
setNotice(t("loginOk"));
|
|
942
|
+
kickFollowUpRefresh();
|
|
943
|
+
}).catch(() => {
|
|
944
|
+
});
|
|
945
|
+
}, LOGIN_POLL_INTERVAL_MS);
|
|
946
|
+
return () => clearInterval(timer);
|
|
947
|
+
}, [loginPending, rpc]);
|
|
792
948
|
const run = async (endpoint) => {
|
|
793
949
|
const isUsageRefresh = endpoint === "usage/refresh";
|
|
950
|
+
const isLogin = endpoint === "login/cli" || endpoint === "login/device";
|
|
794
951
|
if (isUsageRefresh) {
|
|
795
952
|
setUsageBusy(true);
|
|
796
953
|
} else {
|
|
@@ -800,7 +957,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
800
957
|
setNotice("");
|
|
801
958
|
let value;
|
|
802
959
|
try {
|
|
803
|
-
value = await callRpc(rpc, endpoint, {});
|
|
960
|
+
value = await callRpc(rpc, endpoint, {}, isLogin ? LOGIN_CALL_TIMEOUT_MS : RPC_CALL_TIMEOUT_MS);
|
|
804
961
|
if (value?.account || value?.catalog || value?.usage) {
|
|
805
962
|
applyPartial(value);
|
|
806
963
|
} else {
|
|
@@ -809,6 +966,9 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
809
966
|
if (value?.ok === false && value.error) {
|
|
810
967
|
setNotice("");
|
|
811
968
|
setError(value.error);
|
|
969
|
+
} else if (value?.pending === true) {
|
|
970
|
+
setLoginPending({ url: value.loginUrl, code: value.userCode, warning: value.warning });
|
|
971
|
+
setNotice("");
|
|
812
972
|
} else if (value?.ok !== false) {
|
|
813
973
|
const successKey = {
|
|
814
974
|
pull: "pullOk",
|
|
@@ -830,7 +990,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
830
990
|
if (isUsageRefresh) setUsageBusy(false);
|
|
831
991
|
else setBusy("");
|
|
832
992
|
}
|
|
833
|
-
const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error;
|
|
993
|
+
const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error && value?.pending !== true;
|
|
834
994
|
if (shouldFollowUp) kickFollowUpRefresh();
|
|
835
995
|
};
|
|
836
996
|
const account = status?.account;
|
|
@@ -850,12 +1010,24 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
850
1010
|
signedIn && account?.maskedAccount ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsAccount", children: account.maskedAccount }) : null
|
|
851
1011
|
] }),
|
|
852
1012
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
|
|
853
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy), onClick: () => void run("login/cli"), children: t("loginCli") }),
|
|
854
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("login/device"), children: t("loginDevice") }),
|
|
1013
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/cli"), children: t("loginCli") }),
|
|
1014
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/device"), children: t("loginDevice") }),
|
|
855
1015
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("pull"), children: t("pull") }),
|
|
856
1016
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--danger", type: "button", disabled: Boolean(busy) || !signedIn, onClick: () => void run("logout"), children: t("logout") })
|
|
857
1017
|
] }),
|
|
858
1018
|
busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--busy", children: t("busy") }) : null,
|
|
1019
|
+
loginPending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--busy", children: [
|
|
1020
|
+
t("loginWaiting"),
|
|
1021
|
+
loginPending.code ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1022
|
+
" \xB7 ",
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: loginPending.code })
|
|
1024
|
+
] }) : null,
|
|
1025
|
+
loginPending.url ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1026
|
+
" \xB7 ",
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "gsLink", href: loginPending.url, target: "_blank", rel: "noreferrer", children: t("loginOpenBrowser") })
|
|
1028
|
+
] }) : null
|
|
1029
|
+
] }) : null,
|
|
1030
|
+
loginPending?.warning ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginNoUrl") }) : null,
|
|
859
1031
|
notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
|
|
860
1032
|
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
|
|
861
1033
|
t("error"),
|
|
@@ -877,6 +1049,18 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
877
1049
|
] })
|
|
878
1050
|
] })
|
|
879
1051
|
] }),
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1053
|
+
VersionCard,
|
|
1054
|
+
{
|
|
1055
|
+
t,
|
|
1056
|
+
call: (endpoint, payload) => callRpc(
|
|
1057
|
+
rpc,
|
|
1058
|
+
endpoint,
|
|
1059
|
+
payload,
|
|
1060
|
+
endpoint === "plugin/update" ? 19e4 : RPC_CALL_TIMEOUT_MS
|
|
1061
|
+
)
|
|
1062
|
+
}
|
|
1063
|
+
),
|
|
880
1064
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
881
1065
|
UsagePanel,
|
|
882
1066
|
{
|