dsh-packycode-api 1.2.5 → 1.2.7
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/CHANGELOG.md +14 -0
- package/README.en.md +20 -10
- package/README.md +20 -10
- package/lib/client.js +252 -132
- package/lib/index.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.7
|
|
4
|
+
|
|
5
|
+
- **Image keys can be renamed.** Settings → PackyCode → Image generation and editing gains the same **Rename** action as conversation keys, through a new `images/keys/rename` endpoint on the image key ring (`packycode-api/image-keys`). Both cards share one rename form: the name becomes a text field, Enter or **Save** stores it, Escape or **Cancel** keeps the old one, and only the key id and the new label reach the host.
|
|
6
|
+
- **One quota hover for both key rings.** The PackyCode quota beside the model picker now uses the same component as the image-key quota: the help cursor marks it, and hovering or focusing it shows the same lines — which quota it is, the current key, its account, remaining, used and total quota, and when it was read or that the last read failed. The composer figure's details open leftward so they stay inside the composer.
|
|
7
|
+
- **Registration invite in Settings.** While no conversation key is stored, **Accounts and keys** shows PackyCode's registration invite link (`https://www.packyapi.ai/register?aff=Im3l`) under the empty-state note. It is a new-window link, which DSH desktop hands to the system browser (a web profile opens a tab). It appears only after the host has answered the status read and disappears as soon as any key is stored.
|
|
8
|
+
- **README: install with an exact version.** The Plugins page's install prompt shows the newest npm version, but DSH's bundled pnpm 11 resolves a bare `dsh-packycode-api` to the newest version published at least 24 hours ago (`minimumReleaseAge`). During the 1.1.x/1.2.x release burst a first install therefore landed on 1.1.1 or 1.1.5, whose settings channel needed a DSH restart and answered every call with HTTP 405 until then. The install guide now always enters `dsh-packycode-api@<version>` (pnpm then adds it to `minimumReleaseAgeExclude` itself), the FAQ explains the 405 symptom with a version check and the recovery, and the README links the registration invite for readers without a key.
|
|
9
|
+
- Validation, 2026-10-15: 422 offline tests (6 new for key rename and the shared quota hover, 4 for the invite), both rebuilt bundles and the versioned tarball. The first-install 405 was reproduced against the DSH 0.2.0-rc.2 runtime in an isolated `DSH_HOME`: a Plugins-page install of the bare name prompted 1.2.6, installed 1.1.5 and answered HTTP 405 until restart, while `dsh-packycode-api@1.2.6` installed 1.2.6 and answered at once. A browser replay of the built settings page showed the invite in light and dark themes and no invite with a stored key. No DSH restart, paid request or live gateway call.
|
|
10
|
+
|
|
11
|
+
## 1.2.6
|
|
12
|
+
|
|
13
|
+
- Clicking black space around a large image now closes the preview, including the canvas inside the full-size dialog. The image hit area fits its actual displayed dimensions instead of covering letterboxing; image clicks, download controls and error notices keep the viewer open.
|
|
14
|
+
- Added a regression for canvas/dialog whitespace and nested button icons. Browser verification reproduced the old no-op on the black image margin, then confirmed the same click dismisses the updated viewer and restores focus, while clicking the image keeps it open.
|
|
15
|
+
- Validation, 2026-10-15: 412 tests, both rebuilt bundles and the versioned tarball. No paid image request or DSH restart was needed.
|
|
16
|
+
|
|
3
17
|
## 1.2.5
|
|
4
18
|
|
|
5
19
|
- Fixed generated-image clicks doing nothing in DSH desktop. The optional `nativeImageViewer` service is absent in the current desktop build, and its popup policy denies the previous `window.open(blob:...)` fallback. PackyCode now owns an in-app large-image overlay shared by main-chat and tool-detail results, with Escape/close, focus restoration and viewport fitting.
|
package/README.en.md
CHANGED
|
@@ -41,23 +41,25 @@ The `PackyCode` model group ships with the plugin. No YAML edits and no separate
|
|
|
41
41
|
|
|
42
42
|
## Install
|
|
43
43
|
|
|
44
|
-
You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH setup, see the [official instructions](https://github.com/deepseek-ai/deepseek-harness#run).
|
|
44
|
+
You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH setup, see the [official instructions](https://github.com/deepseek-ai/deepseek-harness#run). No API key yet? You can register with PackyCode and apply for one through [this invite link](https://www.packyapi.ai/register?aff=Im3l).
|
|
45
45
|
|
|
46
46
|
### 1. Install the plugin
|
|
47
47
|
|
|
48
|
-
Open the desktop application's **Plugins** page, click **Add plugin**, and enter
|
|
48
|
+
Open the desktop application's **Plugins** page, click **Add plugin**, and enter the package name **with a version** as the **package name or address**:
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
|
-
dsh-packycode-api
|
|
51
|
+
dsh-packycode-api@1.2.7
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Choose the **official** install source, then click **Install**.
|
|
54
|
+
Choose the **official** install source, then click **Install**. `1.2.7` was the latest release when this was written; the [npm page](https://www.npmjs.com/package/dsh-packycode-api) shows the current one.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
> **Always include the version.** With a bare `dsh-packycode-api`, the install prompt shows the newest version on npm, but DSH's bundled pnpm by default installs only versions published at least 24 hours ago, so you can end up several releases behind with no sign of it in the prompt. With an exact version, pnpm installs that version and adds it to the profile's `minimumReleaseAgeExclude` itself. Afterwards, check **Current version** under **Settings → PackyCode → Plugin version**.
|
|
57
|
+
|
|
58
|
+
A **first install requires no DSH restart**: open **Settings → PackyCode** and add a key immediately. This holds from 1.1.7 on; earlier versions need one restart after a first install, see the FAQ. When updating an already loaded older version, follow the restart guidance in the **Plugin version** card.
|
|
57
59
|
|
|
58
60
|
### 2. Add a key
|
|
59
61
|
|
|
60
|
-
Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label is optional and only distinguishes keys. Click **Add**.
|
|
62
|
+
Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label is optional and only distinguishes keys. Click **Add**. Until a key has been added, **Accounts and keys** shows the same registration invite link, which opens in the system browser; it disappears once a key is stored.
|
|
61
63
|
|
|
62
64
|
> Do not paste a key into a public issue, chat, or screenshot. The plugin does not read `PACKY_CODE_API_KEY` from the environment, and it does not silently switch to another provider when a request fails.
|
|
63
65
|
|
|
@@ -70,7 +72,7 @@ Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, `gpt
|
|
|
70
72
|
|
|
71
73
|
The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
|
|
72
74
|
|
|
73
|
-
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.
|
|
75
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.7`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
|
|
74
76
|
|
|
75
77
|
For a local checkout, keep the `link:` prefix: `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`. **Do not pass a bare path or `file:`** — that install copies the whole checkout, `node_modules` included, into the profile, where it then shadows the packages the DSH installation itself provides and the plugin looks uninstalled; see the FAQ. If you already installed that way, uninstall first and reinstall as above.
|
|
76
78
|
|
|
@@ -211,7 +213,7 @@ Only GPT Responses requests keep `prompt_cache_key`; other Responses models omit
|
|
|
211
213
|
|
|
212
214
|
## Update and uninstall
|
|
213
215
|
|
|
214
|
-
**Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling `dsh-packycode-api`
|
|
216
|
+
**Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling from the **Plugins** page works too, again with the version in the package name (for example `dsh-packycode-api@1.2.7`); without it you may get a version that is merely the newest one published at least 24 hours ago.
|
|
215
217
|
|
|
216
218
|
**Coming back from a local-path install, uninstall first.** An update is a `pnpm add`: it rewrites the files inside the package directory and leaves a `node_modules` that an earlier local-path install left there. That older tree keeps shadowing the packages the DSH installation provides, so the plugin row fails to load with no visible reason. **Uninstall** runs `pnpm remove`, which deletes the package directory outright — only a reinstall after that is clean. `node scripts/install-check.mjs` reports whether a profile is in that state.
|
|
217
219
|
|
|
@@ -227,7 +229,15 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
227
229
|
|
|
228
230
|
Output means a leftover tree: a registry install ships no `node_modules`, so this one usually comes from an earlier local-path install (`file:`, or a bare path instead of `link:`). It sits inside the profile directory, and DSH resolves a plugin's imports from inside the profile first, so the plugin gets those old copies — for example `@deepseek-ai/schemastery` 3.18.2, which has no `Schema.volatile()`, so the module throws while it is imported. When an import fails the Loader creates no fiber for that row and no client bundle is composed either, so the model route, the RPC channel, and the settings panel all stay absent.
|
|
229
231
|
|
|
230
|
-
Recovery: **uninstall** the plugin on the **Plugins** page, **install** `dsh-packycode-api`
|
|
232
|
+
Recovery: **uninstall** the plugin on the **Plugins** page, **install** it again with the version in the package name (for example `dsh-packycode-api@1.2.7`), then quit and reopen the desktop application. Uninstall runs `pnpm remove` and deletes the whole package directory; reinstalling or clicking **Update** only rewrites files and keeps the leftover tree. Deleting that `node_modules` by hand and restarting works too.
|
|
233
|
+
|
|
234
|
+
**Right after a first install, every card in Settings shows `transport failure for /packycode-api/…: HTTP 405`?** The installed version is 1.1.6 or earlier, usually because only the bare package name was entered: the install prompt shows the newest version, but pnpm installs the newest one published at least 24 hours ago (see **Install the plugin**). Those older versions can register their settings channel only after DSH restarts and rebuilds its `connection` service. So right after a first install `/packycode-api` does not exist yet, requests fall through to the static page, and it answers 405; the model route and the settings panel itself load normally. From 1.1.7 on, a first install needs no restart. Check which version was installed:
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
grep '"version"' ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/package.json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Recovery: quit and reopen the desktop application, and the old version works. Then click **Check for updates** → **Update plugin** under **Settings → PackyCode → Plugin version** (it installs the exact latest version and is not subject to the 24-hour rule), and restart once more.
|
|
231
241
|
|
|
232
242
|
**Web search says "no stored API key's group covers …"?** The search backend's model is not in your token groups (or the gateway currently routes it to another supplier). Pick the other backend under **Settings → PackyCode → Web search**, or add a key whose group covers it.
|
|
233
243
|
|
|
@@ -243,7 +253,7 @@ Recovery: **uninstall** the plugin on the **Plugins** page, **install** `dsh-pac
|
|
|
243
253
|
|
|
244
254
|
**A rate limit disabled the key?** It should not. HTTP 429 does not rotate. Exhausted quota or HTTP 401 does; after a top-up, click **Re-enable**.
|
|
245
255
|
|
|
246
|
-
**Installing from the Plugins page fails with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm
|
|
256
|
+
**Installing from the Plugins page fails with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** By default pnpm accepts only versions published at least 24 hours ago and checks the whole profile lockfile. (Entering only this plugin's bare name does not raise this error; it silently installs an older version that has passed 24 hours instead, see **Install the plugin**.) The package it names is often another plugin that was just updated, not this one. Retry once it is 24 hours old, or add the reported `name@version` under `minimumReleaseAgeExclude` in the profile's `pnpm-workspace.yaml`. Once this plugin is installed, **Update plugin** in Settings is not subject to that check.
|
|
247
257
|
|
|
248
258
|
**The CLI is installed, but Claude still fails?** A host launched from Finder or the Start menu usually has a shorter `PATH` than a terminal. The plugin also checks the official install location, common package-manager directories, and `npm prefix -g`. If it still cannot find the binary, set `claudeCliPath` or click **Check again**. After a Host change or a reinstall, quit and reopen the desktop application.
|
|
249
259
|
|
package/README.md
CHANGED
|
@@ -41,23 +41,25 @@
|
|
|
41
41
|
|
|
42
42
|
## 安装
|
|
43
43
|
|
|
44
|
-
先准备好 **DeepSeek Harness** 和 **PackyCode(PackyAPI)的 API Key**。尚未安装 DSH,可查看 [DSH 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)
|
|
44
|
+
先准备好 **DeepSeek Harness** 和 **PackyCode(PackyAPI)的 API Key**。尚未安装 DSH,可查看 [DSH 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。还没有 API Key 的,可以通过[这个邀请链接](https://www.packyapi.ai/register?aff=Im3l)注册 PackyCode 申请。
|
|
45
45
|
|
|
46
46
|
### 1. 安装插件
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
打开桌面应用的**插件**页,点击**添加插件**,在**包名或地址**中填入**带版本号**的包名:
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
|
-
dsh-packycode-api
|
|
51
|
+
dsh-packycode-api@1.2.7
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
在**安装源**中选择**官方源**,然后点击**安装**。`1.2.7` 是写这份说明时的最新版,当前最新版本号以 [npm 页面](https://www.npmjs.com/package/dsh-packycode-api) 为准。
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
> **一定要带版本号。** 只填 `dsh-packycode-api` 时,安装前的提示显示的是 npm 上的最新版,但 DSH 自带的 pnpm 默认只安装发布满 24 小时的版本,实际装上的可能是好几个版本之前的旧版,而安装提示里看不出这一点。填写精确版本时,pnpm 会直接安装这个版本,并自动把它加入 profile 的 `minimumReleaseAgeExclude`。装好后可以在 **设置 → PackyCode → 插件版本** 核对「当前版本」。
|
|
57
|
+
|
|
58
|
+
首次安装后**无需重启 DSH**,可直接打开**设置 → PackyCode** 添加 Key。这适用于 1.1.7 及以后的版本;更早的版本首次安装后要重启一次,见「常见问题」。更新已加载的旧版时,按「插件版本」卡片的重启提示操作。
|
|
57
59
|
|
|
58
60
|
### 2. 添加 Key
|
|
59
61
|
|
|
60
|
-
打开 **设置 → PackyCode**,在「添加 Key」里粘贴 API Key。备注可选,用来区分多个 Key
|
|
62
|
+
打开 **设置 → PackyCode**,在「添加 Key」里粘贴 API Key。备注可选,用来区分多个 Key。点「添加」。还没有添加过 Key 时,「账户与 Key」里会显示同一个注册邀请链接,点击后在系统浏览器中打开;添加 Key 之后就不再显示。
|
|
61
63
|
|
|
62
64
|
> 不要把 Key 粘贴到公开 Issue、聊天记录或截图里。插件不会读取环境变量 `PACKY_CODE_API_KEY`,也不会在失败时偷偷改用别的 provider。
|
|
63
65
|
|
|
@@ -70,7 +72,7 @@ dsh-packycode-api
|
|
|
70
72
|
|
|
71
73
|
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
72
74
|
|
|
73
|
-
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.
|
|
75
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.7`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
|
|
74
76
|
|
|
75
77
|
本地开发要加 `link:` 前缀:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。**不要直接写路径(或 `file:`)** —— 那样的安装会把整个仓库目录(连同 `node_modules`)复制进 profile,之后它会在 profile 内部挡住 DSH 自带的包,插件看起来像没装,见「常见问题」。已经那样装过的话,先卸载再按上面的方式重装。
|
|
76
78
|
|
|
@@ -211,7 +213,7 @@ Responses 请求只有 GPT 模型保留 `prompt_cache_key`,其他 Responses
|
|
|
211
213
|
|
|
212
214
|
## 更新与卸载
|
|
213
215
|
|
|
214
|
-
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host
|
|
216
|
+
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装,同样要填写带版本号的包名(例如 `dsh-packycode-api@1.2.7`),否则装上的可能是发布满 24 小时的旧版。
|
|
215
217
|
|
|
216
218
|
**从本地路径安装切回官方源时,必须先卸载。** 「更新」是 `pnpm add`,它只重写包目录里的文件,不会删除更早一次本地路径安装留在包目录内的 `node_modules`;那份旧依赖会继续挡住 DSH 自带的包,插件行加载失败且看不出原因。「卸载」执行的是 `pnpm remove`,会删掉整个包目录,重装才是干净的。用 `node scripts/install-check.mjs` 可以确认当前 profile 是否处于这个状态。
|
|
217
219
|
|
|
@@ -227,7 +229,15 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
227
229
|
|
|
228
230
|
有输出就是残留:从 npm 安装的包不会自带 `node_modules`,这一份通常来自更早一次「本地路径」安装(`file:` 或直接写路径,而不是 `link:`)。它位于 profile 目录内部,而 DSH 解析插件导入时优先使用 profile 内的副本,所以插件拿到的仍是那份旧依赖 —— 例如 `@deepseek-ai/schemastery` 3.18.2 没有 `Schema.volatile()`,模块在导入阶段就抛错。导入失败时 Loader 不会为这一行创建 fiber,客户端 bundle 也不会被组合,于是模型路由、RPC 通道和设置页全都不会出现。
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
修复:到**插件**页**卸载**本插件,再用带版本号的包名重新**安装**(例如 `dsh-packycode-api@1.2.7`),然后完全退出并重新打开桌面应用。卸载执行的是 `pnpm remove`,会删掉整个包目录;直接重装或点「更新」只重写文件,不会清掉这份残留。也可以手动删掉上面那个 `node_modules` 再重启。
|
|
233
|
+
|
|
234
|
+
**首次安装后,设置页每张卡片都显示 `transport failure for /packycode-api/…: HTTP 405`?** 实际装上的是 1.1.6 或更早的版本,通常是因为安装时只填了包名:安装提示显示的是最新版,pnpm 实际装的却是发布满 24 小时的旧版(见「安装插件」)。这些旧版要等 DSH 重启、重建它的 `connection` 服务之后,才能注册设置通道。所以首次安装后 `/packycode-api` 通道还不存在,请求落到静态页面,返回 405;模型路由和设置页本身照常加载。1.1.7 起首次安装不需要重启。先确认装上的版本:
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
grep '"version"' ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/package.json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
处理:完全退出并重新打开桌面应用,旧版即可正常使用。然后在 **设置 → PackyCode → 插件版本** 点「检查更新」→「更新插件」(它会固定安装最新版,不受 24 小时规则限制),再重启一次。
|
|
231
241
|
|
|
232
242
|
**某个模型不可用,但 Key 是有效的?** 看「账户与 Key」里这个 Key 的分组和 ✓ / ✕:分组不覆盖这个模型时,换一个分组合适的 Key,或在 PackyCode 控制台给 Key 加上对应分组。存了分组合适的 Key 时,插件会自动用它跑这个模型。分组拒绝不会停用 Key。
|
|
233
243
|
|
|
@@ -241,7 +251,7 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
241
251
|
|
|
242
252
|
**限速之后 Key 被停用了?** 不该发生。429 不会切换 Key。额度用尽或 401 才会停用;充值后点「重新启用」。
|
|
243
253
|
|
|
244
|
-
**插件页安装报 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm
|
|
254
|
+
**插件页安装报 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm 默认只接受发布满 24 小时的版本,并且会检查 profile 里的整个 lockfile。(只填本插件的包名不会报这个错,而是悄悄装上满 24 小时的旧版,见「安装插件」。)报错里点名的往往是另一个刚更新过的插件,不是本插件。等它发布满 24 小时再重试,或把报错里的 `包名@版本` 加到 profile 目录 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 下。已安装本插件后,设置页的「更新插件」不受这个限制。
|
|
245
255
|
|
|
246
256
|
**网页搜索报「no stored API key's group covers …」?** 所选搜索后端的模型不在你的令牌分组里(或网关暂时把它路由到了别的供应商)。在 **设置 → PackyCode → 网页搜索** 换一个后端,或添加分组覆盖它的 Key。
|
|
247
257
|
|
package/lib/client.js
CHANGED
|
@@ -44,7 +44,6 @@ window.__ModuleLoader__.load({
|
|
|
44
44
|
imagesBalanceFailed: "图片余额读取失败",
|
|
45
45
|
imagesComposerLabel: "图片剩余额度",
|
|
46
46
|
imagesBalanceKey: "当前图片 Key",
|
|
47
|
-
imagesBalanceReadAt: "更新于 {time}",
|
|
48
47
|
imagesBalanceHint: "仅汇总图片 Key 的额度,每分钟自动更新;也可手动刷新。与对话 Key 的余额分开统计。",
|
|
49
48
|
imagesIntro: "使用独立图片 Key 调用 PackyCode 图片接口。生成新图或上传图片后描述修改要求;图片模型独立于对话模型。原始 PNG 保存在本机,聊天中显示图片预览。",
|
|
50
49
|
imagesEnabled: "图片生成与编辑",
|
|
@@ -88,6 +87,8 @@ window.__ModuleLoader__.load({
|
|
|
88
87
|
totalStale: "其中 {count} 个 Key 本次读取失败,显示的是上次读到的余额。",
|
|
89
88
|
staleNote: "上次读取于 {time},本次读取失败:{error}",
|
|
90
89
|
empty: "还没有 Key。添加一个即可在模型选择器里看到 PackyCode 的模型。",
|
|
90
|
+
inviteLead: "还没有 PackyCode 的 API Key?可以通过邀请链接注册:",
|
|
91
|
+
inviteLink: "www.packyapi.ai/register ↗",
|
|
91
92
|
keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个;「切到此 Key」指定优先使用的 Key。",
|
|
92
93
|
keyActive: "使用中",
|
|
93
94
|
keyDisabled: "已停用",
|
|
@@ -137,9 +138,11 @@ window.__ModuleLoader__.load({
|
|
|
137
138
|
apiHost: "模型接口地址",
|
|
138
139
|
statusHost: "余额接口地址",
|
|
139
140
|
composerLabel: "PackyCode 剩余额度",
|
|
141
|
+
composerKey: "当前 Key",
|
|
140
142
|
composerNoKey: "无可用 Key",
|
|
141
143
|
composerUnread: "余额读取失败",
|
|
142
144
|
composerStale: "上次读取于 {time},本次读取失败",
|
|
145
|
+
quotaReadAt: "更新于 {time}",
|
|
143
146
|
cliTitle: "Claude Code CLI",
|
|
144
147
|
cliChecking: "正在检测本机的 Claude Code CLI…",
|
|
145
148
|
cliInstalled: "已安装",
|
|
@@ -226,7 +229,6 @@ window.__ModuleLoader__.load({
|
|
|
226
229
|
imagesBalanceFailed: "Could not read image balances",
|
|
227
230
|
imagesComposerLabel: "Image balance",
|
|
228
231
|
imagesBalanceKey: "Current image key",
|
|
229
|
-
imagesBalanceReadAt: "Updated at {time}",
|
|
230
232
|
imagesBalanceHint: "Only image key quotas are counted here. Updates every minute, or use Refresh. Conversation key balances are separate.",
|
|
231
233
|
imagesIntro: "Use a dedicated image key with PackyCode’s Images API. Describe a new image or attach images with editing instructions. The image engine is independent of the conversation model. Original PNGs are saved locally and previews appear in chat.",
|
|
232
234
|
imagesEnabled: "Generate and edit images",
|
|
@@ -270,6 +272,8 @@ window.__ModuleLoader__.load({
|
|
|
270
272
|
totalStale: "{count} of these keys failed this read and show their last read balance.",
|
|
271
273
|
staleNote: "Last read at {time}; this read failed: {error}",
|
|
272
274
|
empty: "No keys yet. Add one and the PackyCode models appear in the model picker.",
|
|
275
|
+
inviteLead: "No PackyCode API key yet? You can register through this invite link:",
|
|
276
|
+
inviteLink: "www.packyapi.ai/register ↗",
|
|
273
277
|
keysHint: "Used in order: when the active key cannot serve a request, the next one takes over. “Use this key” picks the preferred key.",
|
|
274
278
|
keyActive: "Active",
|
|
275
279
|
keyDisabled: "Disabled",
|
|
@@ -311,9 +315,11 @@ window.__ModuleLoader__.load({
|
|
|
311
315
|
apiHost: "Model endpoint",
|
|
312
316
|
statusHost: "Balance endpoint",
|
|
313
317
|
composerLabel: "PackyCode remaining balance",
|
|
318
|
+
composerKey: "Current key",
|
|
314
319
|
composerNoKey: "No usable key",
|
|
315
320
|
composerUnread: "Balance unavailable",
|
|
316
|
-
composerStale: "
|
|
321
|
+
composerStale: "Last read at {time}; this read failed",
|
|
322
|
+
quotaReadAt: "Updated at {time}",
|
|
317
323
|
cliTitle: "Claude Code CLI",
|
|
318
324
|
cliChecking: "Checking this device for the Claude Code CLI…",
|
|
319
325
|
cliInstalled: "Installed",
|
|
@@ -400,6 +406,8 @@ window.__ModuleLoader__.load({
|
|
|
400
406
|
.pkc-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:28px}
|
|
401
407
|
.pkc-title{display:flex;align-items:center;gap:8px;margin:0;font-size:15px;font-weight:600;line-height:22px}
|
|
402
408
|
.pkc-note{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
409
|
+
.pkc-link{color:var(--dsw-alias-link,#3964fe);font-weight:500;text-decoration:none;word-break:break-all}
|
|
410
|
+
.pkc-link:hover,.pkc-link:focus{outline:none;text-decoration:underline dotted var(--dsw-alias-link,#3964fe);text-underline-offset:3px}
|
|
403
411
|
.pkc-warn{margin:0;color:var(--dsw-alias-state-warn-label,#dd8629);font-size:12px;line-height:18px}
|
|
404
412
|
.pkc-error{margin:0;color:var(--dsw-alias-state-error-primary,#ec1313);font-size:12px;line-height:18px}
|
|
405
413
|
.pkc-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}
|
|
@@ -423,11 +431,12 @@ window.__ModuleLoader__.load({
|
|
|
423
431
|
.pkc-input:disabled{opacity:.5}
|
|
424
432
|
.pkc-image-choice{flex:none;width:100%;max-width:300px;cursor:pointer}
|
|
425
433
|
.pkc-image-shortcut{display:inline-flex;align-items:center;gap:8px}
|
|
426
|
-
.pkc-
|
|
427
|
-
.pkc-
|
|
428
|
-
.pkc-
|
|
429
|
-
.pkc-
|
|
430
|
-
.pkc-
|
|
434
|
+
.pkc-quota{position:relative;display:inline-flex;align-items:center}
|
|
435
|
+
.pkc-quota-value{cursor:help;border-radius:var(--dsw-radius-xs,4px)}
|
|
436
|
+
.pkc-quota-value:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
437
|
+
.pkc-quota-tooltip{position:absolute;bottom:calc(100% + 8px);left:0;z-index:100;box-sizing:border-box;width:max-content;max-width:min(320px,calc(100vw - 32px));padding:10px 12px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv1);font-size:12px;line-height:20px;white-space:pre-line;overflow-wrap:anywhere;visibility:hidden;opacity:0;pointer-events:none}
|
|
438
|
+
.pkc-quota[data-align=end]>.pkc-quota-tooltip{left:auto;right:0}
|
|
439
|
+
.pkc-quota:hover>.pkc-quota-tooltip,.pkc-quota:focus-within>.pkc-quota-tooltip{visibility:visible;opacity:1}
|
|
431
440
|
.pkc-image-result,.pkc-image-chat{display:flex;flex-direction:column;gap:10px;padding:8px 0}
|
|
432
441
|
.pkc-generated-image{display:grid;place-items:center;align-self:flex-start;max-width:100%;width:320px;min-height:80px;padding:0;overflow:hidden;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);cursor:zoom-in}
|
|
433
442
|
.pkc-generated-image img{display:block;max-width:100%;max-height:480px;object-fit:contain}
|
|
@@ -441,7 +450,7 @@ window.__ModuleLoader__.load({
|
|
|
441
450
|
.pkc-image-viewer-button:disabled{opacity:.55;cursor:wait}
|
|
442
451
|
.pkc-image-viewer-close{width:36px;padding:0;border-color:transparent;background:transparent}
|
|
443
452
|
.pkc-image-viewer-canvas{display:flex;align-items:center;justify-content:center;flex:1;min-height:0;overflow:hidden}
|
|
444
|
-
.pkc-image-viewer-canvas img{display:block;width:100%;height:100%;min-width:0;min-height:0;object-fit:contain}
|
|
453
|
+
.pkc-image-viewer-canvas img{display:block;flex:none;width:auto;height:auto;max-width:100%;max-height:100%;min-width:0;min-height:0;object-fit:contain}
|
|
445
454
|
.pkc-image-viewer-error{align-self:center;flex:none;margin:12px 0 0;padding:8px 12px;border-radius:8px;background:#4d2222;color:#ffd3d3;text-align:center}
|
|
446
455
|
.pkc-form{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
|
|
447
456
|
|
|
@@ -720,6 +729,54 @@ window.__ModuleLoader__.load({
|
|
|
720
729
|
})
|
|
721
730
|
] });
|
|
722
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* A composer quota figure with the same details for either key ring. The help
|
|
734
|
+
* cursor marks it; hovering or focusing it shows the key, its account, the
|
|
735
|
+
* remaining, used and total quota and when it was read, and asks the caller
|
|
736
|
+
* for an unforced read.
|
|
737
|
+
* @param props.copy - the ring's own lines: `title`, `key`, `failed`, `loading`.
|
|
738
|
+
* @param props.empty - what the figure shows while the remaining quota is unknown.
|
|
739
|
+
* @param props.align - `end` opens the details leftward, for the composer's right side.
|
|
740
|
+
*/
|
|
741
|
+
function QuotaHover({ row, fetchedAt, loading = false, failed = false, empty, copy, align, onRefresh, t }) {
|
|
742
|
+
const tooltipId = react.useId();
|
|
743
|
+
const usage = row?.usage;
|
|
744
|
+
const unlimited = usage?.unlimitedQuota === true;
|
|
745
|
+
const remaining = unlimited ? t("keyUnlimited") : formatUsd(usage?.availableUsd);
|
|
746
|
+
const stale = usage !== void 0 && (failed || row.stale !== void 0);
|
|
747
|
+
const display = remaining ?? empty;
|
|
748
|
+
const details = [
|
|
749
|
+
copy.title,
|
|
750
|
+
row?.label ? `${copy.key}: ${row.label}` : void 0,
|
|
751
|
+
usage?.name ? `${t("keyName")}: ${usage.name}` : void 0,
|
|
752
|
+
`${t("keyRemaining")}: ${remaining ?? "—"}`,
|
|
753
|
+
`${t("keyUsed")}: ${formatUsd(usage?.usedUsd) ?? "—"}`,
|
|
754
|
+
`${t("accountLimit")}: ${unlimited ? t("keyUnlimited") : formatUsd(quotaTotalUsd(usage)) ?? "—"}`,
|
|
755
|
+
stale ? fill(t("composerStale"), { time: readTime(row.stale?.fetchedAt ?? fetchedAt) }) : failed || row?.error ? copy.failed : loading ? copy.loading : fetchedAt === void 0 ? void 0 : fill(t("quotaReadAt"), { time: readTime(fetchedAt) })
|
|
756
|
+
].filter(Boolean).join("\n");
|
|
757
|
+
const refresh = () => {
|
|
758
|
+
onRefresh?.();
|
|
759
|
+
};
|
|
760
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
761
|
+
className: "pkc-quota",
|
|
762
|
+
"data-align": align,
|
|
763
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
764
|
+
className: "pkc-composer pkc-quota-value",
|
|
765
|
+
role: "status",
|
|
766
|
+
tabIndex: 0,
|
|
767
|
+
"aria-label": `${copy.title}: ${stale ? "≈" : ""}${display}`,
|
|
768
|
+
"aria-describedby": tooltipId,
|
|
769
|
+
onMouseEnter: refresh,
|
|
770
|
+
onFocus: refresh,
|
|
771
|
+
children: stale ? `≈${display}` : display
|
|
772
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
773
|
+
className: "pkc-quota-tooltip",
|
|
774
|
+
id: tooltipId,
|
|
775
|
+
role: "tooltip",
|
|
776
|
+
children: details
|
|
777
|
+
})]
|
|
778
|
+
});
|
|
779
|
+
}
|
|
723
780
|
/** Only this card's keys contribute; account-wide billing is never added. */
|
|
724
781
|
function BalanceTotals({ rows, t }) {
|
|
725
782
|
const totals = sumBalances(rows);
|
|
@@ -779,7 +836,6 @@ window.__ModuleLoader__.load({
|
|
|
779
836
|
function ImageShortcutBalance({ call, t }) {
|
|
780
837
|
const [view, setView] = react.useState({ loading: true });
|
|
781
838
|
const refresh = react.useRef(() => {});
|
|
782
|
-
const tooltipId = react.useId();
|
|
783
839
|
react.useEffect(() => {
|
|
784
840
|
let live = true, generation = 0, pending = false;
|
|
785
841
|
const read = async (force = false, reset = false) => {
|
|
@@ -823,42 +879,22 @@ window.__ModuleLoader__.load({
|
|
|
823
879
|
window.removeEventListener("packycode-images-updated", changed);
|
|
824
880
|
};
|
|
825
881
|
}, [call]);
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
className: "pkc-image-balance",
|
|
843
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
844
|
-
className: "pkc-composer pkc-image-balance-value",
|
|
845
|
-
role: "status",
|
|
846
|
-
tabIndex: 0,
|
|
847
|
-
"aria-label": `${t("imagesComposerLabel")}: ${stale ? "≈" : ""}${display}`,
|
|
848
|
-
"aria-describedby": tooltipId,
|
|
849
|
-
onMouseEnter: () => {
|
|
850
|
-
refresh.current();
|
|
851
|
-
},
|
|
852
|
-
onFocus: () => {
|
|
853
|
-
refresh.current();
|
|
854
|
-
},
|
|
855
|
-
children: stale ? `≈${display}` : display
|
|
856
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
857
|
-
className: "pkc-image-balance-tooltip",
|
|
858
|
-
id: tooltipId,
|
|
859
|
-
role: "tooltip",
|
|
860
|
-
children: details
|
|
861
|
-
})]
|
|
882
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaHover, {
|
|
883
|
+
row: currentImageKey(view.balance),
|
|
884
|
+
fetchedAt: view.balance?.fetchedAt,
|
|
885
|
+
loading: view.loading,
|
|
886
|
+
failed: view.failed === true,
|
|
887
|
+
empty: view.loading ? "…" : "—",
|
|
888
|
+
copy: {
|
|
889
|
+
title: t("imagesComposerLabel"),
|
|
890
|
+
key: t("imagesBalanceKey"),
|
|
891
|
+
failed: t("imagesBalanceFailed"),
|
|
892
|
+
loading: t("imagesBalanceLoading")
|
|
893
|
+
},
|
|
894
|
+
onRefresh: () => {
|
|
895
|
+
refresh.current();
|
|
896
|
+
},
|
|
897
|
+
t
|
|
862
898
|
});
|
|
863
899
|
}
|
|
864
900
|
//#endregion
|
|
@@ -1039,7 +1075,7 @@ window.__ModuleLoader__.load({
|
|
|
1039
1075
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1040
1076
|
className: "pkc-image-backdrop",
|
|
1041
1077
|
onClick: (event) => {
|
|
1042
|
-
if (event.target ===
|
|
1078
|
+
if (event.target.closest("img, button, [role=\"alert\"]") === null) viewer.close();
|
|
1043
1079
|
},
|
|
1044
1080
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1045
1081
|
className: "pkc-image-viewer",
|
|
@@ -1116,6 +1152,79 @@ window.__ModuleLoader__.load({
|
|
|
1116
1152
|
}, image.revision);
|
|
1117
1153
|
}
|
|
1118
1154
|
//#endregion
|
|
1155
|
+
//#region src/client-key-rename.jsx
|
|
1156
|
+
/**
|
|
1157
|
+
* Renaming a stored key, the same way in the conversation and image key
|
|
1158
|
+
* cards: the name becomes a text field; Enter or Save stores it, Escape or
|
|
1159
|
+
* Cancel keeps the old one. Only the key's id and the new label reach the host.
|
|
1160
|
+
*
|
|
1161
|
+
* @module dsh-packycode-api/client-key-rename
|
|
1162
|
+
*/
|
|
1163
|
+
/**
|
|
1164
|
+
* The rename state a key card keeps.
|
|
1165
|
+
* @param save - `({ id, label }) => Promise`; the field closes once it settles.
|
|
1166
|
+
* @returns `{ renaming, start, change, cancel, submit }`.
|
|
1167
|
+
*/
|
|
1168
|
+
function useKeyRename(save) {
|
|
1169
|
+
const [renaming, setRenaming] = react.useState();
|
|
1170
|
+
return {
|
|
1171
|
+
renaming,
|
|
1172
|
+
start: (entry) => setRenaming({
|
|
1173
|
+
id: entry.id,
|
|
1174
|
+
label: entry.label
|
|
1175
|
+
}),
|
|
1176
|
+
change: (label) => setRenaming((current) => ({
|
|
1177
|
+
...current,
|
|
1178
|
+
label
|
|
1179
|
+
})),
|
|
1180
|
+
cancel: () => setRenaming(void 0),
|
|
1181
|
+
submit: () => {
|
|
1182
|
+
if (renaming === void 0 || renaming.label.trim() === "") return;
|
|
1183
|
+
save({
|
|
1184
|
+
id: renaming.id,
|
|
1185
|
+
label: renaming.label
|
|
1186
|
+
}).then(() => setRenaming(void 0));
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/** The field and buttons that stand in for a key's name while it is renamed. */
|
|
1191
|
+
function KeyRenameForm({ rename, busy, t }) {
|
|
1192
|
+
const label = rename.renaming.label;
|
|
1193
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1194
|
+
className: "pkc-form",
|
|
1195
|
+
onSubmit: (event) => {
|
|
1196
|
+
event.preventDefault();
|
|
1197
|
+
rename.submit();
|
|
1198
|
+
},
|
|
1199
|
+
children: [
|
|
1200
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1201
|
+
className: "pkc-input",
|
|
1202
|
+
type: "text",
|
|
1203
|
+
maxLength: 60,
|
|
1204
|
+
autoFocus: true,
|
|
1205
|
+
placeholder: t("keyRenamePlaceholder"),
|
|
1206
|
+
value: label,
|
|
1207
|
+
onChange: (event) => rename.change(event.target.value),
|
|
1208
|
+
onKeyDown: (event) => event.key === "Escape" && rename.cancel()
|
|
1209
|
+
}),
|
|
1210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1211
|
+
type: "submit",
|
|
1212
|
+
className: "pkc-btn",
|
|
1213
|
+
"data-variant": "primary",
|
|
1214
|
+
disabled: busy || label.trim() === "",
|
|
1215
|
+
children: t("keyRenameSave")
|
|
1216
|
+
}),
|
|
1217
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1218
|
+
type: "button",
|
|
1219
|
+
className: "pkc-btn",
|
|
1220
|
+
"data-variant": "ghost",
|
|
1221
|
+
onClick: rename.cancel,
|
|
1222
|
+
children: t("keyRenameCancel")
|
|
1223
|
+
})
|
|
1224
|
+
]
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
//#endregion
|
|
1119
1228
|
//#region src/client-images.jsx
|
|
1120
1229
|
const imageModelLabel = (id) => id.replace("gpt-image-", "GPT Image ").replace("-flare", " Flare").replace("-sunburst", " Sunburst");
|
|
1121
1230
|
/** Only projections come back from the host; the password draft is cleared on success. */
|
|
@@ -1182,6 +1291,10 @@ window.__ModuleLoader__.load({
|
|
|
1182
1291
|
if (mounted.current) setBusy(false);
|
|
1183
1292
|
}
|
|
1184
1293
|
};
|
|
1294
|
+
const rename = useKeyRename(({ id, label }) => mutate("images/keys/rename", {
|
|
1295
|
+
id,
|
|
1296
|
+
label
|
|
1297
|
+
}));
|
|
1185
1298
|
const disabled = busy || state?.writable !== true;
|
|
1186
1299
|
const models = state?.models ?? [];
|
|
1187
1300
|
const qualities = models.find((model) => model.id === state?.imageModel)?.qualities ?? [];
|
|
@@ -1291,7 +1404,11 @@ window.__ModuleLoader__.load({
|
|
|
1291
1404
|
className: "pkc-row-top",
|
|
1292
1405
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1293
1406
|
className: "pkc-row-name",
|
|
1294
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1407
|
+
children: [rename.renaming?.id === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRenameForm, {
|
|
1408
|
+
rename,
|
|
1409
|
+
busy,
|
|
1410
|
+
t
|
|
1411
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1295
1412
|
className: "pkc-name",
|
|
1296
1413
|
children: entry.label
|
|
1297
1414
|
}), entry.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1299,26 +1416,37 @@ window.__ModuleLoader__.load({
|
|
|
1299
1416
|
"data-tone": "success",
|
|
1300
1417
|
children: t("keyActive")
|
|
1301
1418
|
})]
|
|
1302
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1419
|
+
}), rename.renaming?.id !== entry.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1303
1420
|
className: "pkc-actions",
|
|
1304
|
-
children: [
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1421
|
+
children: [
|
|
1422
|
+
!entry.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1423
|
+
className: "pkc-btn",
|
|
1424
|
+
type: "button",
|
|
1425
|
+
disabled: busy,
|
|
1426
|
+
onClick: () => {
|
|
1427
|
+
mutate("images/keys/select", { id: entry.id });
|
|
1428
|
+
},
|
|
1429
|
+
children: t("keyUse")
|
|
1430
|
+
}),
|
|
1431
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1432
|
+
className: "pkc-btn",
|
|
1433
|
+
"data-variant": "ghost",
|
|
1434
|
+
type: "button",
|
|
1435
|
+
disabled: busy,
|
|
1436
|
+
onClick: () => rename.start(entry),
|
|
1437
|
+
children: t("keyRename")
|
|
1438
|
+
}),
|
|
1439
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1440
|
+
className: "pkc-btn",
|
|
1441
|
+
"data-variant": "danger",
|
|
1442
|
+
type: "button",
|
|
1443
|
+
disabled: busy,
|
|
1444
|
+
onClick: () => {
|
|
1445
|
+
mutate("images/keys/remove", { id: entry.id });
|
|
1446
|
+
},
|
|
1447
|
+
children: t("keyRemove")
|
|
1448
|
+
})
|
|
1449
|
+
]
|
|
1322
1450
|
})]
|
|
1323
1451
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyBalance, {
|
|
1324
1452
|
row: rows.find((row) => row.id === entry.id),
|
|
@@ -1607,6 +1735,8 @@ window.__ModuleLoader__.load({
|
|
|
1607
1735
|
const PROVIDER = "packycode";
|
|
1608
1736
|
const CHANNEL = "/packycode-api";
|
|
1609
1737
|
const REFRESH_MS = 6e4;
|
|
1738
|
+
/** PackyCode registration through the author's invite link, offered only until a key is stored. */
|
|
1739
|
+
const INVITE_URL = "https://www.packyapi.ai/register?aff=Im3l";
|
|
1610
1740
|
const INSTALL_POLL_MS = 2e3;
|
|
1611
1741
|
/** How long after a search change the panel re-reads what DSH applied (the patch hot-reloads). */
|
|
1612
1742
|
const SEARCH_SETTLE_MS = 1500;
|
|
@@ -1748,8 +1878,8 @@ window.__ModuleLoader__.load({
|
|
|
1748
1878
|
})
|
|
1749
1879
|
] });
|
|
1750
1880
|
}
|
|
1751
|
-
function KeyRow({ entry, row, group, descriptions, groupsLoading, busy,
|
|
1752
|
-
const editing = renaming?.id === entry.id;
|
|
1881
|
+
function KeyRow({ entry, row, group, descriptions, groupsLoading, busy, rename, onAction, t }) {
|
|
1882
|
+
const editing = rename.renaming?.id === entry.id;
|
|
1753
1883
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1754
1884
|
className: "pkc-row",
|
|
1755
1885
|
children: [
|
|
@@ -1758,35 +1888,10 @@ window.__ModuleLoader__.load({
|
|
|
1758
1888
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1759
1889
|
className: "pkc-row-name",
|
|
1760
1890
|
children: [
|
|
1761
|
-
editing ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
onRename.submit();
|
|
1766
|
-
},
|
|
1767
|
-
children: [
|
|
1768
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1769
|
-
className: "pkc-input",
|
|
1770
|
-
type: "text",
|
|
1771
|
-
maxLength: 60,
|
|
1772
|
-
autoFocus: true,
|
|
1773
|
-
placeholder: t("keyRenamePlaceholder"),
|
|
1774
|
-
value: renaming.label,
|
|
1775
|
-
onChange: (event) => onRename.change(event.target.value),
|
|
1776
|
-
onKeyDown: (event) => event.key === "Escape" && onRename.cancel()
|
|
1777
|
-
}),
|
|
1778
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1779
|
-
type: "submit",
|
|
1780
|
-
variant: "primary",
|
|
1781
|
-
disabled: busy || renaming.label.trim() === "",
|
|
1782
|
-
children: t("keyRenameSave")
|
|
1783
|
-
}),
|
|
1784
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1785
|
-
variant: "ghost",
|
|
1786
|
-
onClick: onRename.cancel,
|
|
1787
|
-
children: t("keyRenameCancel")
|
|
1788
|
-
})
|
|
1789
|
-
]
|
|
1891
|
+
editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRenameForm, {
|
|
1892
|
+
rename,
|
|
1893
|
+
busy,
|
|
1894
|
+
t
|
|
1790
1895
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1791
1896
|
className: "pkc-name",
|
|
1792
1897
|
children: entry.label
|
|
@@ -1818,7 +1923,7 @@ window.__ModuleLoader__.load({
|
|
|
1818
1923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1819
1924
|
variant: "ghost",
|
|
1820
1925
|
disabled: busy,
|
|
1821
|
-
onClick: () =>
|
|
1926
|
+
onClick: () => rename.start(entry),
|
|
1822
1927
|
children: t("keyRename")
|
|
1823
1928
|
}),
|
|
1824
1929
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
@@ -1843,6 +1948,27 @@ window.__ModuleLoader__.load({
|
|
|
1843
1948
|
]
|
|
1844
1949
|
});
|
|
1845
1950
|
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Where to get a key, for a profile that has none. A new-window link: the
|
|
1953
|
+
* desktop shell hands http(s) window opens to the system browser, and a web
|
|
1954
|
+
* profile opens a new tab.
|
|
1955
|
+
*/
|
|
1956
|
+
function RegisterInvite({ t }) {
|
|
1957
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1958
|
+
className: "pkc-note",
|
|
1959
|
+
children: [
|
|
1960
|
+
t("inviteLead"),
|
|
1961
|
+
" ",
|
|
1962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1963
|
+
className: "pkc-link",
|
|
1964
|
+
href: INVITE_URL,
|
|
1965
|
+
target: "_blank",
|
|
1966
|
+
rel: "noopener noreferrer",
|
|
1967
|
+
children: t("inviteLink")
|
|
1968
|
+
})
|
|
1969
|
+
]
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1846
1972
|
function AccountsCard({ call, t }) {
|
|
1847
1973
|
const [status, setStatus] = react.useState();
|
|
1848
1974
|
const [balance, setBalance] = react.useState();
|
|
@@ -1854,7 +1980,6 @@ window.__ModuleLoader__.load({
|
|
|
1854
1980
|
key: "",
|
|
1855
1981
|
label: ""
|
|
1856
1982
|
});
|
|
1857
|
-
const [renaming, setRenaming] = react.useState();
|
|
1858
1983
|
const generation = react.useRef(0);
|
|
1859
1984
|
const readGroups = react.useCallback((force = false) => {
|
|
1860
1985
|
setGroupsLoading(true);
|
|
@@ -1901,24 +2026,10 @@ window.__ModuleLoader__.load({
|
|
|
1901
2026
|
label: ""
|
|
1902
2027
|
}));
|
|
1903
2028
|
};
|
|
1904
|
-
const rename = {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
}),
|
|
1909
|
-
change: (label) => setRenaming((current) => ({
|
|
1910
|
-
...current,
|
|
1911
|
-
label
|
|
1912
|
-
})),
|
|
1913
|
-
cancel: () => setRenaming(void 0),
|
|
1914
|
-
submit: () => {
|
|
1915
|
-
if (renaming === void 0 || renaming.label.trim() === "") return;
|
|
1916
|
-
mutate("keys/rename", {
|
|
1917
|
-
id: renaming.id,
|
|
1918
|
-
label: renaming.label
|
|
1919
|
-
}).then(() => setRenaming(void 0));
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
2029
|
+
const rename = useKeyRename(({ id, label }) => mutate("keys/rename", {
|
|
2030
|
+
id,
|
|
2031
|
+
label
|
|
2032
|
+
}));
|
|
1922
2033
|
const keys = status?.keys ?? [];
|
|
1923
2034
|
const rows = balance?.keys ?? [];
|
|
1924
2035
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
@@ -1937,10 +2048,10 @@ window.__ModuleLoader__.load({
|
|
|
1937
2048
|
})]
|
|
1938
2049
|
}),
|
|
1939
2050
|
children: [
|
|
1940
|
-
keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2051
|
+
keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1941
2052
|
className: "pkc-note",
|
|
1942
2053
|
children: t("empty")
|
|
1943
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2054
|
+
}), status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegisterInvite, { t })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1944
2055
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BalanceTotals, {
|
|
1945
2056
|
rows,
|
|
1946
2057
|
t
|
|
@@ -1954,8 +2065,7 @@ window.__ModuleLoader__.load({
|
|
|
1954
2065
|
descriptions: groups?.descriptions,
|
|
1955
2066
|
groupsLoading,
|
|
1956
2067
|
busy,
|
|
1957
|
-
|
|
1958
|
-
onRename: rename,
|
|
2068
|
+
rename,
|
|
1959
2069
|
onAction: mutate,
|
|
1960
2070
|
t
|
|
1961
2071
|
}, entry.id))
|
|
@@ -2634,38 +2744,48 @@ window.__ModuleLoader__.load({
|
|
|
2634
2744
|
const call = useCaller(rpc);
|
|
2635
2745
|
const enabled = react.useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot())?.current?.provider === PROVIDER;
|
|
2636
2746
|
const [balance, setBalance] = react.useState();
|
|
2747
|
+
const refresh = react.useRef(() => {});
|
|
2637
2748
|
react.useEffect(() => {
|
|
2638
2749
|
if (!enabled) {
|
|
2639
2750
|
setBalance(void 0);
|
|
2640
2751
|
return;
|
|
2641
2752
|
}
|
|
2642
2753
|
let live = true;
|
|
2754
|
+
let pending = false;
|
|
2643
2755
|
const tick = () => {
|
|
2756
|
+
if (pending) return;
|
|
2757
|
+
pending = true;
|
|
2644
2758
|
call("balance").then((value) => {
|
|
2645
2759
|
if (live) setBalance(value);
|
|
2646
2760
|
}).catch(() => {
|
|
2647
2761
|
if (live) setBalance(void 0);
|
|
2762
|
+
}).finally(() => {
|
|
2763
|
+
pending = false;
|
|
2648
2764
|
});
|
|
2649
2765
|
};
|
|
2766
|
+
refresh.current = tick;
|
|
2650
2767
|
tick();
|
|
2651
2768
|
const timer = window.setInterval(tick, REFRESH_MS);
|
|
2652
2769
|
return () => {
|
|
2653
2770
|
live = false;
|
|
2771
|
+
refresh.current = () => {};
|
|
2654
2772
|
window.clearInterval(timer);
|
|
2655
2773
|
};
|
|
2656
2774
|
}, [call, enabled]);
|
|
2657
2775
|
if (!enabled || balance === void 0) return null;
|
|
2658
2776
|
const row = activeUsage(balance);
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2777
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaHover, {
|
|
2778
|
+
row,
|
|
2779
|
+
fetchedAt: balance.fetchedAt,
|
|
2780
|
+
empty: row?.error === void 0 ? t("composerNoKey") : t("composerUnread"),
|
|
2781
|
+
copy: {
|
|
2782
|
+
title: t("composerLabel"),
|
|
2783
|
+
key: t("composerKey"),
|
|
2784
|
+
failed: t("composerUnread")
|
|
2785
|
+
},
|
|
2786
|
+
align: "end",
|
|
2787
|
+
onRefresh: () => refresh.current(),
|
|
2788
|
+
t
|
|
2669
2789
|
});
|
|
2670
2790
|
}
|
|
2671
2791
|
const inject = [
|
package/lib/index.js
CHANGED
|
@@ -5631,6 +5631,7 @@ function createImageController({ ring, balance, readPreference, writePreference,
|
|
|
5631
5631
|
async mutate(operation, payload = {}) {
|
|
5632
5632
|
const operations = {
|
|
5633
5633
|
add: () => ring.add(payload.key, payload.label),
|
|
5634
|
+
rename: () => ring.rename(String(payload.id ?? ""), payload.label),
|
|
5634
5635
|
remove: () => ring.remove(String(payload.id ?? "")),
|
|
5635
5636
|
select: () => ring.select(String(payload.id ?? "")),
|
|
5636
5637
|
enable: () => ring.enable(String(payload.id ?? ""))
|
|
@@ -7080,6 +7081,10 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
|
|
|
7080
7081
|
key: payload?.key,
|
|
7081
7082
|
label: payload?.label
|
|
7082
7083
|
}),
|
|
7084
|
+
"images/keys/rename": (payload) => imageSupport().mutate("rename", {
|
|
7085
|
+
id: payload?.id,
|
|
7086
|
+
label: payload?.label
|
|
7087
|
+
}),
|
|
7083
7088
|
"images/keys/remove": (payload) => imageSupport().mutate("remove", { id: payload?.id }),
|
|
7084
7089
|
"images/keys/select": (payload) => imageSupport().mutate("select", { id: payload?.id }),
|
|
7085
7090
|
"images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Use PackyCode API keys in DeepSeek Harness with multi-protocol models, image generation and editing, separate key rings, and balance display",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|