dsh-prompt-enhance 0.2.7 → 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 +2 -2
- package/README.zh-CN.md +2 -2
- package/cordis.patch.yml +3 -2
- package/docs/release-0.2.5.md +28 -0
- package/docs/release-0.2.7.md +22 -0
- package/docs/release-0.3.0.md +36 -0
- package/lib/client.js +165 -14
- package/lib/index.js +152 -39
- package/lib/index.js.map +3 -3
- package/package.json +13 -14
- package/src/client/EnhanceButton.tsx +18 -11
- package/src/client/ResultPanel.tsx +1 -0
- package/src/client/enhance-client.ts +1 -0
- package/src/client/index.tsx +42 -23
- package/src/client/locales.ts +4 -2
- package/src/client/settings-source.ts +262 -0
- package/src/config.ts +12 -2
- package/src/enhance-routes.ts +77 -9
- package/src/enhancer.ts +19 -1
- package/src/index.ts +31 -58
- package/src/settings-source.ts +221 -0
- package/src/shared/protocol.ts +18 -0
package/README.md
CHANGED
|
@@ -47,13 +47,13 @@ flowchart LR
|
|
|
47
47
|
|
|
48
48
|
The plugin is one npm package with two halves, following the dsh plugin conventions:
|
|
49
49
|
|
|
50
|
-
- **Host half** (`exports "."`, Node):
|
|
50
|
+
- **Host half** (`exports "."`, Node): declares the `prompt-enhance` settings schema (schemastery — the built-in plugin config page renders it straight from the loader config, and the plugin **re-reads it on every request** instead of caching the boot snapshot), registers the `POST /prompt-enhance/enhance` route on the shared webserver (loopback-fenced, body-capped), and the `/enhance` slash command. The model call goes through `ctx.llm.stream` with a normalized output pass — the same auxiliary-call discipline the harness applies to session titles: deadline + caller cancellation rechecked during and after the stream, terminal-finish validation, tool-call rejection.
|
|
51
51
|
- **Browser half** (`exports "./client"`): registers the enhance button into the `conversation.input.right` slot and the undo bar into `conversation.input.dock`, binds a live mirror of the settings namespace, and installs the global shortcut. All copy is localized (zh/en) through the harness locale system.
|
|
52
52
|
|
|
53
53
|
## Requirements
|
|
54
54
|
|
|
55
55
|
- `dsh >= 0.1.1-rc.2 < 0.3.0` — the range states what has actually been exercised, not an open-ended promise.
|
|
56
|
-
- Boot-verified on `0.1.1-rc.2`, `0.1.2-alpha.3`, `0.1.7-rc.2`,
|
|
56
|
+
- Boot-verified on `0.1.1-rc.2`, `0.1.2-alpha.3`, `0.1.7-rc.2`, `0.2.0-rc.2`, and **`0.2.1-rc.2`** (layer mounts, enhance route answers, client bundle builds; on alpha the plugin takes the newer `ctx.settings.installSection` registration path, and on `0.2.0-rc.2` it also passes the runtime's plugin-compatibility gate). The `0.2.1-rc.2` check is a **real-host end-to-end smoke**: with the local build installed into a temporary profile, the enhance route returned a real model result; setting that profile's `maxInputChars` to `300` made a 400-character draft fail with `422 {count: 400, max: 300}`, and after editing the same profile to `20000` — **without restarting** — the identical draft went through, so a settings edit reaches the *next* request instead of staying at the boot-time snapshot. The `0.2.0-rc.2` checks include the **dsh desktop application** — the environment this plugin is normally used in. On a `0.2.x` runtime expect some *other* plugins to disappear from the UI: the gate skips any bundle whose `peerDependencies` pin an older `@deepseek-ai/dsh-*` range (observed: `dshmarket`, `dsh-find-plugin`, `dsh-better-sidebar`). The harness has broken several of the APIs this plugin touches: `@deepseek-ai/dsh-settings` changed its registration entry between the two early lines, from `0.1.5-rc.3` the settings service became `SettingsForms` (no synchronous `get`), and `0.2.x` **deleted the `installSettingsSection` / `settingsNamespace` exports outright**; `dsh-agent-default-model` moved the harness default model behind its own `ctx.agentDefaultModel.currentSelection()` service; and the browser-side settings mirror moved from `ctx.settingsScope` to `ctx.configForms`. The plugin probes every generation at runtime and adapts, no configuration needed.
|
|
57
57
|
- The ceiling covers the whole `0.2.x` line (including its prereleases); a `0.3.x` is untested and therefore outside the range. `dsh.engines` is advisory metadata read by the loader and plugin manager — it states support, it does not enforce it.
|
|
58
58
|
- Node `^22.19.0 || >=24.0.0` (for building from source)
|
|
59
59
|
- Browser peers `react` / `react-dom` (`^18.2.0`) come from the web shell's module registry. The host-half module `@deepseek-ai/dsh-llm` is a **runtime module supplied by the dsh host and is not installed from npm** (the `lib/` bundles keep every package import external), so it is declared as an *optional* peer: an install that does not provide it produces no warning, and the plugin reports a normal error at enhance time rather than failing to load.
|
package/README.zh-CN.md
CHANGED
|
@@ -47,13 +47,13 @@ flowchart LR
|
|
|
47
47
|
|
|
48
48
|
插件是单个 npm 包、双半区结构,完全遵循 dsh 插件规范:
|
|
49
49
|
|
|
50
|
-
- **宿主半区**(`exports "."`,Node)
|
|
50
|
+
- **宿主半区**(`exports "."`,Node):声明 `prompt-enhance` 设置 schema(schemastery,由内置插件配置页按加载器配置直接渲染,插件在**每次请求时**重新读取,而不是缓存启动快照),注册共享 webserver 上的 `POST /prompt-enhance/enhance` 路由(仅回环、限长),以及 `/enhance` 斜杠命令。模型调用走 `ctx.llm.stream` 并做规范化处理——与 harness 对会话标题相同的辅助调用纪律:超时与调用方取消在流过程中和结束后复查、终结 finish 校验、拒绝工具调用。
|
|
51
51
|
- **浏览器半区**(`exports "./client"`):把增强按钮注册进 `conversation.input.right` 插槽、撤销条注册进 `conversation.input.dock` 插槽,绑定设置命名空间的实时镜像,并安装全局快捷键。全部文案经 harness locale 系统提供中英双语。
|
|
52
52
|
|
|
53
53
|
## 环境要求
|
|
54
54
|
|
|
55
55
|
- `dsh >= 0.1.1-rc.2 < 0.3.0` —— 这个区间表示**实际验证过的范围**,而不是无限期的承诺。
|
|
56
|
-
- 实测环境:`0.1.1-rc.2`、`0.1.2-alpha.3`、`0.1.7-rc.2`、`0.2.0-rc.2
|
|
56
|
+
- 实测环境:`0.1.1-rc.2`、`0.1.2-alpha.3`、`0.1.7-rc.2`、`0.2.0-rc.2`、**`0.2.1-rc.2`** 均已 boot 验证(插件层挂载、增强路由应答、客户端 bundle 构建通过;alpha 上走新版 `ctx.settings.installSection` 注册路径,`0.2.0-rc.2` 上还通过了运行时的插件兼容性闸门)。`0.2.1-rc.2` 的验证是**真实宿主的端到端冒烟**:在临时 profile 里装入本地构建后,增强路由对着在线模型返回了真实结果;把该 profile 的 `maxInputChars` 设为 `300` 后,400 字草稿被 `422 {count:400,max:300}` 拒绝,随后**不重启**把它改成 `20000`,同一份草稿立即通过——设置改动确实作用到下一次请求,而不是停在启动时的快照。其中 `0.2.0-rc.2` 的验证包含 **dsh 桌面应用**——也就是本插件日常实际运行的环境。在 `0.2.x` 运行时上,可能会发现**别的**插件从界面消失:凡 `peerDependencies` 把 `@deepseek-ai/dsh-*` 锁在旧区间的 bundle 都会被闸门跳过(实测被跳过的是 `dshmarket`、`dsh-find-plugin`、`dsh-better-sidebar`)。宿主在插件用到的几处 API 上做过破坏性变更:`@deepseek-ai/dsh-settings` 的注册入口在两条早期版本线之间变过,`0.1.5-rc.3` 起 settings 服务改为 `SettingsForms`(不再有同步 `get`),而 `0.2.x` 干脆**删掉了 `installSettingsSection` / `settingsNamespace` 导出**;`dsh-agent-default-model` 把全局默认模型收进了自己的 `ctx.agentDefaultModel.currentSelection()` 服务;浏览器侧的设置镜像则从 `ctx.settingsScope` 迁到了 `ctx.configForms`。插件对每一代形态均做运行时探测并自动适配,无需配置。
|
|
57
57
|
- 上界覆盖整条 `0.2.x` 线(含其预发布版本);`0.3.x` 尚未验证,因此不在区间内。`dsh.engines` 是加载器与插件管理器读取的**声明性元数据**——它表达支持范围,并不强制拦截。
|
|
58
58
|
- Node `^22.19.0 || >=24.0.0`(仅从源码构建时需要)
|
|
59
59
|
- 浏览器侧 peer `react` / `react-dom`(`^18.2.0`)由 Web 壳的模块注册表提供。宿主半区用到的 `@deepseek-ai/dsh-llm` 是**由 dsh 宿主提供的运行时模块,不从 npm 安装**(`lib/` 产物把包引用全部保持为外部),因此声明为**可选** peer:未提供它的环境不会出现安装警告,增强时按正常错误路径报错,而不是加载失败。
|
package/cordis.patch.yml
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
# `dsh plugin --profile web add link:<repo>/dsh-prompt-enhance`.
|
|
5
5
|
#
|
|
6
6
|
# One package, both halves: the node half (exports ".") runs in the host
|
|
7
|
-
# process and registers the POST /prompt-enhance/enhance route
|
|
8
|
-
# /enhance slash command, and the "prompt-enhance" settings
|
|
7
|
+
# process and registers the POST /prompt-enhance/enhance route and the
|
|
8
|
+
# /enhance slash command, and declares the "prompt-enhance" settings schema
|
|
9
|
+
# this entry's config page renders (read live, per request); the
|
|
9
10
|
# browser half (declared by `dsh.client`, served from lib/client.js) adds
|
|
10
11
|
# the composer enhance button (conversation.input.right), the preview
|
|
11
12
|
# panel, and the undo bar (conversation.input.dock).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Two metadata corrections
|
|
2
|
+
|
|
3
|
+
No runtime behavior changes. Both corrections remove claims the project could not back.
|
|
4
|
+
|
|
5
|
+
### `dsh.engines.dsh` is now bounded: `>=0.1.1-rc.2 <0.2.0`
|
|
6
|
+
|
|
7
|
+
It previously read `>=0.1.1-rc.2` — an open-ended claim that *any* future harness version is supported. Boot verification at the time covered `0.1.1-rc.2`, `0.1.2-alpha.3`, and `0.1.7-rc.2`, while the current npm line was already `0.2.0-rc.2`, so the open ceiling was over-promising.
|
|
8
|
+
|
|
9
|
+
`<0.2.0` rather than `<0.1.8` is deliberate: per semver, a prerelease only matches a range that names a prerelease, so `<0.1.8` would have **excluded the verified `0.1.7-rc.2`** — the exact version the range has to keep. `<0.2.0` keeps every `0.1.x` and excludes the then-unverified `0.2.0-rc.1` / `0.2.0-rc.2`.
|
|
10
|
+
|
|
11
|
+
> Superseded by **0.2.6**, which widened the ceiling to `<0.3.0` after `0.2.0-rc.2` was verified on the dsh desktop application. This release is the honest intermediate step, not the final state.
|
|
12
|
+
|
|
13
|
+
### `dsh.client.inject` no longer lists `@deepseek-ai/dsh-client-ui-settings`
|
|
14
|
+
|
|
15
|
+
That entry dated from the layout in which the browser half hard-declared the settings surface — the declaration that produced, on a profile without that package:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
web boot: 1 entry did not activate
|
|
19
|
+
dsh-prompt-enhance: pending (waiting for service: settingsScope)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
…and took the whole GUI down with it (fixed in 0.2.3 by reaching every optional service through `ctx.inject`). With that fix in place the module no longer belongs in the required-provider list.
|
|
23
|
+
|
|
24
|
+
### Verification
|
|
25
|
+
|
|
26
|
+
- `npm run typecheck`, `npm run build` clean.
|
|
27
|
+
- **207 tests across 20 files** pass.
|
|
28
|
+
- Metadata-only: `lib/` is byte-identical to 0.2.4, which is why the tarball contents are unchanged apart from `package.json`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Version bump carrying the desktop-app verification record
|
|
2
|
+
|
|
3
|
+
No runtime behavior changes. This version exists because a **docs-only** change followed 0.2.6: the source had already been verified on the current dsh line, but the record of *where* it was verified needed correcting.
|
|
4
|
+
|
|
5
|
+
### What this version carries
|
|
6
|
+
|
|
7
|
+
The `0.2.6` release notes described the `0.2.0-rc.2` verification as having happened on the global CLI. That was incomplete: the environment the plugin is **actually used in day to day** is the dsh **desktop application**, and its `FileVersion` is `0.2.0-rc.2` too. The record now names both hosts, and the READMEs say so as well.
|
|
8
|
+
|
|
9
|
+
This matters for anyone reading the support claim: "verified on `0.2.0-rc.2`" should not rest on a CLI smoke test when the desktop app is the real target. Both were checked, and the evidence is the same three signals:
|
|
10
|
+
|
|
11
|
+
1. **The runtime's plugin-compatibility gate accepts this plugin.** `0.2.0-rc.2` refuses bundles whose `peerDependencies` pin an older `@deepseek-ai/dsh-*` range. This plugin passes, thanks to the optional `@deepseek-ai/dsh-llm` peer (`>=0.1.1-rc.2`) introduced in 0.2.4. The gate skipped `dshmarket@1.45.0`, `dsh-find-plugin@0.4.0`, and `dsh-better-sidebar@0.15.2` on the same host.
|
|
12
|
+
2. **No `pending (waiting for service: …)` and no `1 entry did not activate`.**
|
|
13
|
+
3. **A real enhancement completed end to end**: `[prompt-enhance] … in=2 out=64 ctx=0 1928ms ok`.
|
|
14
|
+
|
|
15
|
+
### Documentation-only
|
|
16
|
+
|
|
17
|
+
`src/` and `lib/` are unchanged from 0.2.6; the shipped bundles are identical. The declared range is `>=0.1.1-rc.2 <0.3.0`, boot-verified on `0.1.1-rc.2`, `0.1.2-alpha.3`, `0.1.7-rc.2`, and `0.2.0-rc.2` (desktop app and CLI).
|
|
18
|
+
|
|
19
|
+
### Verification
|
|
20
|
+
|
|
21
|
+
- `npm run typecheck`, `npm run build` clean.
|
|
22
|
+
- **207 tests across 20 files** pass.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## Adapting to the `0.2.1-rc.2` settings contract
|
|
2
|
+
|
|
3
|
+
No new features. This release exists because the harness moved the two services that carry the plugin's settings, and the plugin's probes for them **failed silently**: the settings page kept rendering, edits kept landing on disk, and nothing ever reached a request or the composer. Five defects are fixed; three of them predate the update but only became reachable on the current line.
|
|
4
|
+
|
|
5
|
+
### What was silently broken
|
|
6
|
+
|
|
7
|
+
| Surface | What `0.2.x` did | What the user saw | Fix |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Host: the section | Deleted the `installSettingsSection` / `settingsNamespace` exports outright; `SettingsForms.describe()` replaced them | Settings edits applied on disk, never to the next enhance | Read the row through `describe()` **per request** |
|
|
10
|
+
| Browser: the mirror | `ctx.settingsScope` no longer exists | `enabled` / `maxInputChars` / `shortcut` / `streaming` stuck at the bundled defaults | `configForms.get(entryId)` first, then the aggregate binder, then the legacy scope |
|
|
11
|
+
| Browser: the attachment guard | `InputState.imageIds` renamed `attachmentIds` | The "attachments only" guard never fired | Read both names |
|
|
12
|
+
|
|
13
|
+
The host fix is verified end to end, not just in unit tests: on a real `dsh web` at `0.2.1-rc.2`, a profile with `maxInputChars: 300` refused a 400-character draft with `422 {count: 400, max: 300}`, and editing the same profile to `20000` — **without restarting** — let the identical draft through.
|
|
14
|
+
|
|
15
|
+
### Two defects in the new browser mirror
|
|
16
|
+
|
|
17
|
+
Found on a second pass over the code this release introduces.
|
|
18
|
+
|
|
19
|
+
1. **It disposed an object it does not own.** `configForms.get()` does not create a form per caller: the settings provider caches one controller per entry and hands the same instance to the settings page, and `dispose()` permanently stops that controller deriving — the provider keeps the dead instance in its cache. Tearing it down on plugin unload would have frozen this entry's settings page for the rest of the session. The mirror now releases only its own subscription.
|
|
20
|
+
2. **It resolved the entry id once.** With the describe mirror not yet loaded, the namespace list was empty, so the plugin bound its configured candidate — which on an aggregate bundle is not the real row, and a namespace that does not exist answers `unavailable` forever. Resolution now re-runs on every describe tick and rebinds when the served id changes, which also covers a rename the first pass could not see.
|
|
21
|
+
|
|
22
|
+
### Three behaviour fixes
|
|
23
|
+
|
|
24
|
+
- **Retry storms.** The rate window counts successful calls only, by design — a run of timeouts must not lock the user out when the model recovers. That left deterministic failures (wrong key, mistyped provider) bounded by nothing but the concurrency cap. Three consecutive failures now trip a 30-second cool-down answering `429 {code: 'cooldown', retryAfterSeconds, failures}` before a body is read or the model is called; a success clears the streak.
|
|
25
|
+
- **`413` killed the socket.** The over-length answer used `res.socket.destroy()`, which also tore down anything else riding the same keep-alive connection. It now answers `connection: close` and lets Node discard the unread body.
|
|
26
|
+
- **A rebuilt webserver left the plugin routeless.** Registration was guarded by a bare "already mounted on this context" flag, so a host that replaced its `webServer` service kept the button on screen while every click 404'd. The guard now compares service identity and re-mounts.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
Nothing to do. The package is a drop-in replacement for `0.2.7`; the declared range is unchanged at `>=0.1.1-rc.2 <0.3.0`, and both older settings APIs are still probed, so a `0.1.x` host keeps working.
|
|
31
|
+
|
|
32
|
+
### Verification
|
|
33
|
+
|
|
34
|
+
- `npm run typecheck` and `npm run build` clean against `@deepseek-ai/dsh-*@0.2.1-rc.2`; a clean rebuild of `lib/` is byte-identical.
|
|
35
|
+
- **231 tests across 22 files** pass, including two new files (`tests/settings-source.test.ts`, `tests/client-settings-source.test.ts`) that pin the probe order, the rebind-on-settle path, and the "never dispose the provider's form" contract.
|
|
36
|
+
- Real-host smoke on `dsh web` at `0.2.1-rc.2`: no `pending` / `did not activate`, the enhance route answered with a real model result (`deepseek-official` / `deepseek-flash`), and the settings schema appeared in the config page with Chinese descriptions.
|
package/lib/client.js
CHANGED
|
@@ -65,6 +65,11 @@ function checkInputText(text, maxChars) {
|
|
|
65
65
|
// src/shared/protocol.ts
|
|
66
66
|
var ENHANCE_ENDPOINT = "/prompt-enhance/enhance";
|
|
67
67
|
var ENHANCE_STREAM_ENDPOINT = "/prompt-enhance/enhance-stream";
|
|
68
|
+
var PROMPT_ENHANCE_ROW_IDS = [
|
|
69
|
+
"prompt-enhance",
|
|
70
|
+
"dsh-prompt-enhance",
|
|
71
|
+
"web-ui-prompt-enhance"
|
|
72
|
+
];
|
|
68
73
|
|
|
69
74
|
// src/client/enhance-client.ts
|
|
70
75
|
var EnhanceClientError = class extends Error {
|
|
@@ -80,6 +85,7 @@ var KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
80
85
|
"rejected",
|
|
81
86
|
"rate-limit",
|
|
82
87
|
"concurrency-limit",
|
|
88
|
+
"cooldown",
|
|
83
89
|
"timeout",
|
|
84
90
|
"upstream",
|
|
85
91
|
"unconfigured",
|
|
@@ -243,6 +249,8 @@ function localizedErrorMessage(t, code, params) {
|
|
|
243
249
|
return t("error.rateLimit", params);
|
|
244
250
|
case "concurrency-limit":
|
|
245
251
|
return t("error.concurrencyLimit", params);
|
|
252
|
+
case "cooldown":
|
|
253
|
+
return t("error.cooldown", params);
|
|
246
254
|
case "timeout":
|
|
247
255
|
return t("error.timeout", params);
|
|
248
256
|
case "unconfigured":
|
|
@@ -637,7 +645,10 @@ function EnhanceButton(props) {
|
|
|
637
645
|
const draft = useInput((state) => stringOf(state.draft));
|
|
638
646
|
const phase = useInput((state) => state.phase);
|
|
639
647
|
const occurrenceCount = useInput((state) => countOf(state.occurrences));
|
|
640
|
-
const
|
|
648
|
+
const attachmentCount = useInput((state) => {
|
|
649
|
+
const record = state;
|
|
650
|
+
return countOf(record.attachmentIds) + countOf(record.imageIds);
|
|
651
|
+
});
|
|
641
652
|
const settings = (0, import_react3.useSyncExternalStore)(subscribeClientSettings, getClientSettings);
|
|
642
653
|
const panel = (0, import_react3.useSyncExternalStore)(subscribe, getPanel);
|
|
643
654
|
const rootRef = (0, import_react3.useRef)(null);
|
|
@@ -651,9 +662,9 @@ function EnhanceButton(props) {
|
|
|
651
662
|
openError(uiKey, draft, { code: "rejected", localized: disabled });
|
|
652
663
|
return;
|
|
653
664
|
}
|
|
654
|
-
if (
|
|
655
|
-
const
|
|
656
|
-
openError(uiKey, draft, { code: "rejected", localized:
|
|
665
|
+
if (attachmentCount > 0 && draft.trim() === "") {
|
|
666
|
+
const attachmentsOnly = t("error.attachmentsOnly");
|
|
667
|
+
openError(uiKey, draft, { code: "rejected", localized: attachmentsOnly });
|
|
657
668
|
return;
|
|
658
669
|
}
|
|
659
670
|
const check = checkInputText(draft, settings.maxInputChars);
|
|
@@ -687,7 +698,7 @@ function EnhanceButton(props) {
|
|
|
687
698
|
return;
|
|
688
699
|
}
|
|
689
700
|
requestEnhance({ sessionId: wireId, text: draft }, controller.signal).then(settle, fail);
|
|
690
|
-
}, [anyBusy, draft,
|
|
701
|
+
}, [anyBusy, draft, attachmentCount, occurrenceCount, phase, settings, uiKey, wireId, t]);
|
|
691
702
|
const runRef = (0, import_react3.useRef)(start);
|
|
692
703
|
(0, import_react3.useEffect)(() => {
|
|
693
704
|
runRef.current = start;
|
|
@@ -799,7 +810,7 @@ var zh = {
|
|
|
799
810
|
"undo.dismiss": "\u5173\u95ED\u63D0\u793A",
|
|
800
811
|
"error.empty": "\u8F93\u5165\u6846\u4E3A\u7A7A\uFF0C\u8BF7\u5148\u8F93\u5165\u8981\u589E\u5F3A\u7684\u63D0\u793A\u8BCD\u3002",
|
|
801
812
|
"error.tooLong": "\u5185\u5BB9\u5171 {count} \u4E2A\u5B57\u7B26\uFF0C\u8D85\u8FC7 {max} \u4E2A\u5B57\u7B26\u4E0A\u9650\uFF08\u6309 Unicode \u5B57\u7B26\u6570\u7EDF\u8BA1\uFF0C\u4E0D\u662F token \u6570\uFF09\u3002\u4E3A\u907F\u514D\u6539\u53D8\u539F\u610F\u4E0D\u4F1A\u81EA\u52A8\u622A\u65AD\uFF0C\u8BF7\u7CBE\u7B80\u540E\u518D\u8BD5\u3002",
|
|
802
|
-
"error.
|
|
813
|
+
"error.attachmentsOnly": "\u8F93\u5165\u6846\u53EA\u9644\u52A0\u4E86\u9644\u4EF6\uFF08\u56FE\u7247\u6216\u6587\u4EF6\uFF09\uFF0C\u8BF7\u8865\u5145\u8981\u589E\u5F3A\u7684\u6587\u672C\u3002",
|
|
803
814
|
"error.occurrences": "\u8F93\u5165\u5185\u5BB9\u5305\u542B\u547D\u4EE4\u6216\u6587\u4EF6\u5F15\u7528\uFF0C\u6682\u4E0D\u652F\u6301\u589E\u5F3A\uFF1B\u8BF7\u5148\u79FB\u9664\u540E\u518D\u8BD5\u3002",
|
|
804
815
|
"error.phase": "\u5F53\u524D\u8F93\u5165\u6B63\u88AB\u5360\u7528\uFF08\u63D0\u4EA4\u4E2D\uFF09\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
|
|
805
816
|
"error.disabled": "\u63D0\u793A\u8BCD\u589E\u5F3A\u5DF2\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u5173\u95ED\u3002",
|
|
@@ -810,6 +821,7 @@ var zh = {
|
|
|
810
821
|
"error.internal": "\u589E\u5F3A\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\uFF1B\u539F\u8F93\u5165\u672A\u6539\u52A8\u3002",
|
|
811
822
|
"error.rateLimit": "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF1A\u6BCF\u5206\u949F\u6700\u591A {limit} \u6B21\u589E\u5F3A\uFF0C\u8BF7\u5728 {retryAfterSeconds} \u79D2\u540E\u518D\u8BD5\u3002",
|
|
812
823
|
"error.concurrencyLimit": "\u5DF2\u6709 {max} \u4E2A\u589E\u5F3A\u6B63\u5728\u8FDB\u884C\uFF0C\u8BF7\u7B49\u5176\u4E2D\u4E00\u4E2A\u5B8C\u6210\u540E\u518D\u8BD5\u3002",
|
|
824
|
+
"error.cooldown": "\u8FDE\u7EED {failures} \u6B21\u589E\u5F3A\u5931\u8D25\uFF0C\u5DF2\u6682\u505C\u8C03\u7528\u4EE5\u514D\u6301\u7EED\u51B2\u51FB\u6A21\u578B\u670D\u52A1\uFF1B\u8BF7 {retryAfterSeconds} \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u5148\u68C0\u67E5\u8BBE\u7F6E\u91CC\u7684\u6A21\u578B\u914D\u7F6E\u3002",
|
|
813
825
|
"error.upstream.auth": "\u9274\u6743\u5931\u8D25\uFF1A\u8BF7\u68C0\u67E5\u8BE5 provider \u7684 API Key \u914D\u7F6E\u3002",
|
|
814
826
|
"error.upstream.invalidCredential": "\u9274\u6743\u5931\u8D25\uFF1A\u5B58\u50A8\u7684 API Key \u4E0D\u53EF\u7528\uFF0C\u8BF7\u4FEE\u6B63\u540E\u91CD\u8BD5\u3002",
|
|
815
827
|
"error.upstream.rateLimit": "\u6A21\u578B\u670D\u52A1\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
|
|
@@ -844,7 +856,7 @@ var en = {
|
|
|
844
856
|
"undo.dismiss": "Dismiss",
|
|
845
857
|
"error.empty": "The input box is empty \u2014 type a prompt to enhance first.",
|
|
846
858
|
"error.tooLong": "The draft is {count} characters, above the {max} cap (Unicode characters, not tokens). It is never auto-truncated (that would change your meaning) \u2014 please shorten it.",
|
|
847
|
-
"error.
|
|
859
|
+
"error.attachmentsOnly": "The composer only has attachments; add the text you want enhanced.",
|
|
848
860
|
"error.occurrences": "The draft contains commands or file references, which are not supported yet \u2014 remove them first.",
|
|
849
861
|
"error.phase": "The input box is busy (submitting) \u2014 try again in a moment.",
|
|
850
862
|
"error.disabled": "Prompt enhance is disabled in the plugin settings.",
|
|
@@ -855,6 +867,7 @@ var en = {
|
|
|
855
867
|
"error.internal": "Enhancement failed. Retry; your draft is untouched.",
|
|
856
868
|
"error.rateLimit": "Too many requests \u2014 at most {limit} enhancements per minute; retry in {retryAfterSeconds} seconds.",
|
|
857
869
|
"error.concurrencyLimit": "{max} enhancements are already running \u2014 wait for one to finish, then retry.",
|
|
870
|
+
"error.cooldown": "{failures} enhancements failed in a row; calls are paused to stop hammering the model service. Retry in {retryAfterSeconds}s, or check the model settings first.",
|
|
858
871
|
"error.upstream.auth": "Authentication failed \u2014 check the API key configured for this provider.",
|
|
859
872
|
"error.upstream.invalidCredential": "Authentication failed \u2014 the stored API key is invalid; fix it and retry.",
|
|
860
873
|
"error.upstream.rateLimit": "The model provider is rate-limiting; retry later.",
|
|
@@ -1108,6 +1121,146 @@ function matchesShortcut(event, combo) {
|
|
|
1108
1121
|
return event.ctrlKey === combo.ctrl && event.altKey === combo.alt && event.shiftKey === combo.shift && event.metaKey === combo.meta && (event.code === combo.code || event.key.toLowerCase() === combo.key);
|
|
1109
1122
|
}
|
|
1110
1123
|
|
|
1124
|
+
// src/client/settings-source.ts
|
|
1125
|
+
var RANK = {
|
|
1126
|
+
none: 0,
|
|
1127
|
+
legacyScope: 1,
|
|
1128
|
+
binder: 2,
|
|
1129
|
+
configForms: 3
|
|
1130
|
+
};
|
|
1131
|
+
function sectionOf(snapshot) {
|
|
1132
|
+
if (snapshot === null || typeof snapshot !== "object") return void 0;
|
|
1133
|
+
const record = snapshot;
|
|
1134
|
+
if (record.status === "unavailable") return void 0;
|
|
1135
|
+
return record.value;
|
|
1136
|
+
}
|
|
1137
|
+
function servedNamespaces(forms) {
|
|
1138
|
+
try {
|
|
1139
|
+
const namespaces = forms.describe?.()?.getSnapshot?.()?.view?.namespaces;
|
|
1140
|
+
return Array.isArray(namespaces) ? namespaces.map((entry) => String(entry?.ns ?? "")) : [];
|
|
1141
|
+
} catch {
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function installClientSettingsSource(ctx, options) {
|
|
1146
|
+
let rank = RANK.none;
|
|
1147
|
+
const adopt = (next, section) => {
|
|
1148
|
+
if (section === void 0 || next < rank) return;
|
|
1149
|
+
rank = next;
|
|
1150
|
+
options.onSection(section);
|
|
1151
|
+
};
|
|
1152
|
+
const adoptSnapshot = (next, form) => {
|
|
1153
|
+
try {
|
|
1154
|
+
const section = sectionOf(form.getSnapshot?.());
|
|
1155
|
+
if (section !== void 0) adopt(next, section);
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
ctx.inject(["configForms"], (formsCtx) => {
|
|
1160
|
+
const forms = formsCtx.configForms;
|
|
1161
|
+
if (forms === void 0 || typeof forms.get !== "function") return;
|
|
1162
|
+
let boundId;
|
|
1163
|
+
let unsubscribeForm;
|
|
1164
|
+
const resolveEntryId = () => {
|
|
1165
|
+
const served = servedNamespaces(forms);
|
|
1166
|
+
return options.rowIds.find((id) => served.includes(id)) ?? served.find((ns) => ns.includes("prompt-enhance")) ?? options.rowIds[0];
|
|
1167
|
+
};
|
|
1168
|
+
const bind = () => {
|
|
1169
|
+
const entryId = resolveEntryId();
|
|
1170
|
+
if (entryId === void 0 || entryId === boundId) return;
|
|
1171
|
+
try {
|
|
1172
|
+
unsubscribeForm?.();
|
|
1173
|
+
} catch {
|
|
1174
|
+
}
|
|
1175
|
+
unsubscribeForm = void 0;
|
|
1176
|
+
boundId = entryId;
|
|
1177
|
+
let form;
|
|
1178
|
+
try {
|
|
1179
|
+
form = forms.get?.(entryId);
|
|
1180
|
+
} catch {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (form === void 0) return;
|
|
1184
|
+
adoptSnapshot(RANK.configForms, form);
|
|
1185
|
+
try {
|
|
1186
|
+
unsubscribeForm = form.subscribe?.(() => adoptSnapshot(RANK.configForms, form));
|
|
1187
|
+
} catch {
|
|
1188
|
+
unsubscribeForm = void 0;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
bind();
|
|
1192
|
+
ctx.effect(() => () => {
|
|
1193
|
+
try {
|
|
1194
|
+
unsubscribeForm?.();
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
}, "prompt-enhance: settings form mirror");
|
|
1198
|
+
try {
|
|
1199
|
+
const watch = forms.describe?.()?.subscribe?.(() => bind());
|
|
1200
|
+
if (typeof watch === "function") {
|
|
1201
|
+
ctx.effect(() => () => {
|
|
1202
|
+
try {
|
|
1203
|
+
watch();
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
}, "prompt-enhance: settings namespace watch");
|
|
1207
|
+
}
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
ctx.inject(["webUiSettings"], (binderCtx) => {
|
|
1212
|
+
const binder = binderCtx.webUiSettings;
|
|
1213
|
+
if (binder === void 0 || typeof binder.bind !== "function") return;
|
|
1214
|
+
let bound;
|
|
1215
|
+
for (const namespace of options.rowIds) {
|
|
1216
|
+
try {
|
|
1217
|
+
bound = binder.bind({ namespace });
|
|
1218
|
+
} catch {
|
|
1219
|
+
bound = void 0;
|
|
1220
|
+
}
|
|
1221
|
+
if (bound !== void 0) break;
|
|
1222
|
+
}
|
|
1223
|
+
if (bound === void 0) return;
|
|
1224
|
+
adoptSnapshot(RANK.binder, bound);
|
|
1225
|
+
let unsubscribe;
|
|
1226
|
+
try {
|
|
1227
|
+
unsubscribe = bound.subscribe?.(() => adoptSnapshot(RANK.binder, bound));
|
|
1228
|
+
} catch {
|
|
1229
|
+
unsubscribe = void 0;
|
|
1230
|
+
}
|
|
1231
|
+
ctx.effect(() => () => {
|
|
1232
|
+
try {
|
|
1233
|
+
unsubscribe?.();
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
}, "prompt-enhance: settings binder mirror");
|
|
1237
|
+
});
|
|
1238
|
+
ctx.inject(["settingsScope"], (scopeCtx) => {
|
|
1239
|
+
const scopeCtxLike = scopeCtx;
|
|
1240
|
+
const scope = scopeCtxLike.settingsScope;
|
|
1241
|
+
if (scope === void 0 || typeof scope.bind !== "function") return;
|
|
1242
|
+
let bound;
|
|
1243
|
+
try {
|
|
1244
|
+
bound = scope.bind({ namespace: options.rowIds[0] ?? "prompt-enhance", decode: (value) => value });
|
|
1245
|
+
} catch {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
adoptSnapshot(RANK.legacyScope, bound);
|
|
1249
|
+
let unsubscribe;
|
|
1250
|
+
try {
|
|
1251
|
+
unsubscribe = bound.subscribe?.(() => adoptSnapshot(RANK.legacyScope, bound));
|
|
1252
|
+
} catch {
|
|
1253
|
+
unsubscribe = void 0;
|
|
1254
|
+
}
|
|
1255
|
+
ctx.effect(() => () => {
|
|
1256
|
+
try {
|
|
1257
|
+
unsubscribe?.();
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
}, "prompt-enhance: settings scope mirror");
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1111
1264
|
// src/client/index.tsx
|
|
1112
1265
|
var inject = [];
|
|
1113
1266
|
function apply(ctx) {
|
|
@@ -1123,16 +1276,13 @@ function apply(ctx) {
|
|
|
1123
1276
|
}, "dsh-prompt-enhance: dictionaries");
|
|
1124
1277
|
});
|
|
1125
1278
|
setClientSettings(decodeClientSettings(void 0));
|
|
1126
|
-
ctx
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
setClientSettings(scope.getSnapshot().value ?? decodeClientSettings(void 0));
|
|
1130
|
-
};
|
|
1131
|
-
sync();
|
|
1132
|
-
ctx.effect(() => scope.subscribe(sync), "dsh-prompt-enhance: settings mirror");
|
|
1279
|
+
installClientSettingsSource(ctx, {
|
|
1280
|
+
rowIds: PROMPT_ENHANCE_ROW_IDS,
|
|
1281
|
+
onSection: (section) => setClientSettings(decodeClientSettings(section))
|
|
1133
1282
|
});
|
|
1134
1283
|
ctx.inject(["slots"], (slotsCtx) => {
|
|
1135
1284
|
const slots = slotsCtx.slots;
|
|
1285
|
+
if (slots === void 0) return;
|
|
1136
1286
|
return slots.inject("conversation.input.right", () => {
|
|
1137
1287
|
try {
|
|
1138
1288
|
return slots.register(
|
|
@@ -1147,6 +1297,7 @@ function apply(ctx) {
|
|
|
1147
1297
|
});
|
|
1148
1298
|
ctx.inject(["slots"], (slotsCtx) => {
|
|
1149
1299
|
const slots = slotsCtx.slots;
|
|
1300
|
+
if (slots === void 0) return;
|
|
1150
1301
|
return slots.inject("conversation.input.dock", () => {
|
|
1151
1302
|
try {
|
|
1152
1303
|
return slots.register(
|