@jayyuen66/dsh-ocr-review 0.1.3 → 0.1.4-rc.1
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 +5 -2
- package/README.zh-CN.md +5 -2
- package/client.js +69 -66
- package/cordis.patch.yml +1 -1
- package/host.js +536 -139
- package/package.json +37 -33
- package/scripts/get-cred.mjs +83 -9
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ English · [中文](README.zh-CN.md)
|
|
|
20
20
|
- Platforms: macOS and Linux are the targets. Process reaping (the reaper guard around `ocr_review` / `ocr_scan`) depends on a POSIX toolchain - bash traps and job control plus the process-group semantics of `ps` / `pgrep` / `kill`. None of that exists on Windows, so the package runs the bare command there (guarded by `process.platform`): no reaping, but it still works. The cost is that a hard host exit can leave an orphaned OCR process.
|
|
21
21
|
- The provider list and the key status come from the official host channels `ctx.settings.describe()` (the `value.providers` of the `llm-pi-ai` entry) and `ctx.credentials`.
|
|
22
22
|
- When either is missing the card shows the degradation reason while tools and settings keep working.
|
|
23
|
-
- The host must be `>=0.2.
|
|
23
|
+
- The host must be `>= 0.2.1-alpha.1`: declared in `peerDependencies` (the host checks it on plugin install from 0.1.7-rc; alpha.1 has no such gate yet) and in `engines.dsh` (same value, read by nothing).
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
@@ -30,7 +30,10 @@ dsh plugin --profile web add @jayyuen66/dsh-ocr-review
|
|
|
30
30
|
|
|
31
31
|
- The packages are on the public npm registry, so installation needs no credentials.
|
|
32
32
|
- The published files are only `host.js`, `client.js`, `cordis.patch.yml` and `scripts` (`prepack` rebuilds both bundles); source repository: see `repository.url` in package.json.
|
|
33
|
-
-
|
|
33
|
+
- No `@deepseek-ai/*` goes in `dependencies`, and that is not a style preference. Consumers of this package are dsh plugins installed into a profile (`nodeLinker: hoisted`, `autoInstallPeers: false`), so any host package listed under `dependencies` is written into that profile as a **real directory**, coexisting with the copy the host process already loaded. `@deepseek-ai/dsh-tools` keys its `TOOL_RUNTIME_SCHEDULER` with a plain `Symbol` (not `Symbol.for`), so symbol identity is per module instance: the host's `dsh-agent-loop` reads a `ToolRuntime` mounted from the duplicate using its own copy's symbol, gets `undefined`, and **every native tool call throws** `Cannot read properties of undefined (reading 'prepare')` — the whole profile loses its tools. This package's build, typecheck and tests all stay green right up to that point, so `test/publish-manifest.test.ts` pins the invariant in the manifest instead.
|
|
34
|
+
- The four host packages this bundle imports as **values** are required (non-optional) `peerDependencies`: `@deepseek-ai/schemastery` (the host's fork of schemastery — only it implements the 0.1.7 `.volatile()` resolution), `@deepseek-ai/dsh-credentials`, `@deepseek-ai/dsh-home-paths` and `@deepseek-ai/dsh-atomic-write`. The host provides them at runtime, and a profile installs no peers, so no duplicate appears. `build-host.mjs` externalizes `dependencies`, `peerDependencies` *and* `optionalDependencies`, so `host.js` keeps the bare specifiers unchanged.
|
|
35
|
+
- Those four are repeated in `devDependencies` — that is what lets this repository's own typecheck, build and tests resolve them. Every other `@deepseek-ai/*` is type-only (erased at build, no specifier left in `host.js`) and stays in `devDependencies` alone.
|
|
36
|
+
- What is left in `dependencies` is genuinely this package's own runtime code: `@jayyuen66/dsh-plugin-shared` and `js-yaml` (the last one used only by `scripts/get-cred.mjs`).
|
|
34
37
|
|
|
35
38
|
## pnpm blocks the dependency script on install (ERR_PNPM_IGNORED_BUILDS)
|
|
36
39
|
|
package/README.zh-CN.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- 平台:目标平台是 macOS / Linux。进程收割(`ocr_review` / `ocr_scan` 的 reaper 守护)依赖 POSIX 工具链——bash 的 trap 与作业控制,加上 `ps` / `pgrep` / `kill` 的进程组语义。Windows 上这层整体缺席,本包按 `process.platform` 直接放行裸命令(不收割但能跑);代价是宿主硬退出后 OCR 可能留下孤儿进程。
|
|
20
20
|
- provider 清单与 key 状态走宿主官方通道 `ctx.settings.describe()`(`llm-pi-ai` 那条的 `value.providers`)与 `ctx.credentials`。
|
|
21
21
|
- 两者缺席时卡片显示降级原因,工具与设置照常可用。
|
|
22
|
-
- 宿主版本要求 `>=0.2.
|
|
22
|
+
- 宿主版本要求 `>= 0.2.1-alpha.1`:写在 `peerDependencies`(0.1.7-rc 起宿主装插件时校验它;alpha.1 还没有这道门)与 `engines.dsh`(同值、无人读)。
|
|
23
23
|
|
|
24
24
|
## 安装
|
|
25
25
|
|
|
@@ -56,7 +56,10 @@ allowBuilds:
|
|
|
56
56
|
**关于这个 postinstall:** 它在正常路径下什么都不做。平台二进制由上游的 `optionalDependencies`(`@alibaba-group/ocr-<os>-<arch>`,各自带 `os` / `cpu` 字段,pnpm 只装匹配当前平台的那个)提供,`install.js` 检测到就打印 `Binary provided by platform package, skipping download.` 后直接返回。实测放行与不放行都能让 `ocr` 正常执行,差别只是要不要在安装期跑那段脚本——它只在平台包装不上时才下载兜底,而在 pnpm 下平台包必然装得上。
|
|
57
57
|
|
|
58
58
|
如果你更希望完全不执行安装期脚本,可以写 `'@alibaba-group/open-code-review': false`:`ocr` 同样可用(launcher 直接从平台包目录取二进制),但宿主 `readPendingBuilds()` 只认值为 `set this to true or false` 的条目,改成 `false` 后官方审批流程就不再能代为授权,只能手工维护。
|
|
59
|
-
-
|
|
59
|
+
- `dependencies` 里不放任何 `@deepseek-ai/*`,这不是风格偏好。本包的消费方是 dsh 插件,被装进 profile(`nodeLinker: hoisted`、`autoInstallPeers: false`),只要宿主包出现在 `dependencies`,pnpm 就会把它在那颗 profile 里落成**真实目录**,与宿主进程已经加载的那份并存。而 `@deepseek-ai/dsh-tools` 的 `TOOL_RUNTIME_SCHEDULER` 是普通 `Symbol`(不是 `Symbol.for`),符号身份按模块实例计算——宿主 `dsh-agent-loop` 拿自己那份的 Symbol 去读由副本挂载出来的 `ToolRuntime`,得到 `undefined`,于是**每一次原生工具调用都抛** `Cannot read properties of undefined (reading 'prepare')`,整个 profile 的工具一起失效。本包的构建、类型检查与测试在这之前全部照绿,所以判据只能由 `test/publish-manifest.test.ts` 落在 manifest 面。
|
|
60
|
+
- 产物里以**值**导入的四枚宿主包是非 optional 的 `peerDependencies`:`@deepseek-ai/schemastery`(宿主 fork 的 schemastery,0.1.7 的 `.volatile()` 解析只在它有实现)、`@deepseek-ai/dsh-credentials`、`@deepseek-ai/dsh-home-paths`、`@deepseek-ai/dsh-atomic-write`。运行期由宿主在场提供,而 profile 不装 peer,所以不产生副本;`build-host.mjs` 对 `dependencies`、`peerDependencies` 与 `optionalDependencies` 三张表一律外部化,`host.js` 里的裸说明符因此一字不变。
|
|
61
|
+
- 这四枚同时另列一份进 `devDependencies`——那才是本包自己的 typecheck、build、test 解析得到它们的方式。其余 `@deepseek-ai/*` 全是 type-only(构建即擦除,`host.js` 里不留说明符),只留在 `devDependencies`。
|
|
62
|
+
- `dependencies` 剩下的确实是本包自己的运行期代码:`@jayyuen66/dsh-plugin-shared` 与 `js-yaml`(后者只被 `scripts/get-cred.mjs` 用)。
|
|
60
63
|
|
|
61
64
|
## 在 dsh 里启用
|
|
62
65
|
|
package/client.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// GENERATED by buildClient() in build-client.mjs — edit src/client-entry.ts, then run `node build-client.mjs`.
|
|
2
2
|
window.__ModuleLoader__.load({
|
|
3
|
-
id:
|
|
3
|
+
id: "@jayyuen66/dsh-ocr-review",
|
|
4
4
|
factory: (require) => {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
'use strict';
|
|
6
|
+
const module = { exports: {} };
|
|
7
|
+
let exports = module.exports;
|
|
7
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
9
|
let react = require("react");
|
|
9
10
|
//#region src/ui-messages.ts
|
|
10
11
|
const UI_MESSAGES = {
|
|
11
12
|
zh: {
|
|
12
13
|
cardTitle: "open-code-review 代码审查",
|
|
13
|
-
cardDescription: "ocr 工具配置:选择 provider/model 应用到 OCR
|
|
14
|
+
cardDescription: "ocr 工具配置:选择 provider/model 应用到 OCR(应用后 key 由 api_key_cmd 动态读取,不再写入明文)+ 审查参数设置",
|
|
14
15
|
statusReadOnly: "当前作用域只读",
|
|
15
16
|
statusDirty: "有未保存的修改,点「保存」生效",
|
|
16
17
|
statusClean: "无未保存的修改",
|
|
@@ -18,6 +19,7 @@ const UI_MESSAGES = {
|
|
|
18
19
|
saving: "保存中…",
|
|
19
20
|
revert: "撤销",
|
|
20
21
|
saveFailed: "保存失败:",
|
|
22
|
+
saveRefused: "未写入(宿主拒收或跳过):{fields}",
|
|
21
23
|
loadProvidersFailed: "加载 providers 失败:{error}(端点未就绪?重启 dsh 后重试)",
|
|
22
24
|
loadingProviders: "加载 providers…",
|
|
23
25
|
noKeySuffix: "(未配置 key)",
|
|
@@ -68,7 +70,7 @@ const UI_MESSAGES = {
|
|
|
68
70
|
},
|
|
69
71
|
en: {
|
|
70
72
|
cardTitle: "open-code-review code review",
|
|
71
|
-
cardDescription: "ocr tool setup: pick a provider/model and apply it to OCR (keys are read dynamically via api_key_cmd
|
|
73
|
+
cardDescription: "ocr tool setup: pick a provider/model and apply it to OCR (once applied, keys are read dynamically via api_key_cmd and no longer written in plain text) + review parameters",
|
|
72
74
|
statusReadOnly: "This scope is read-only",
|
|
73
75
|
statusDirty: "Unsaved changes — press Save to apply",
|
|
74
76
|
statusClean: "No unsaved changes",
|
|
@@ -76,6 +78,7 @@ const UI_MESSAGES = {
|
|
|
76
78
|
saving: "Saving…",
|
|
77
79
|
revert: "Revert",
|
|
78
80
|
saveFailed: "save failed: ",
|
|
81
|
+
saveRefused: "not written (host refused or skipped): {fields}",
|
|
79
82
|
loadProvidersFailed: "failed to load providers: {error} (endpoint not ready? restart dsh and retry)",
|
|
80
83
|
loadingProviders: "loading providers…",
|
|
81
84
|
noKeySuffix: " (no key configured)",
|
|
@@ -126,14 +129,12 @@ const UI_MESSAGES = {
|
|
|
126
129
|
}
|
|
127
130
|
};
|
|
128
131
|
//#endregion
|
|
129
|
-
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
132
|
+
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.3_@deepseek-ai+cordis@4.0.5-alpha.1_@deepseek-ai+dsh-c_6c028874c2202469b2088f67c885abe7/node_modules/@jayyuen66/dsh-plugin-shared/dist/record.js
|
|
133
|
+
function e(e) {
|
|
134
|
+
return typeof e == `object` && !!e && !Array.isArray(e);
|
|
133
135
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return isRecord(value) ? value[key] : void 0;
|
|
136
|
+
function t(t, n) {
|
|
137
|
+
return e(t) ? t[n] : void 0;
|
|
137
138
|
}
|
|
138
139
|
//#endregion
|
|
139
140
|
//#region src/client-entry.ts
|
|
@@ -176,43 +177,43 @@ function errorSummary(error) {
|
|
|
176
177
|
}
|
|
177
178
|
function modelOf(raw) {
|
|
178
179
|
return {
|
|
179
|
-
id: strOf(
|
|
180
|
-
name: strOf(
|
|
180
|
+
id: strOf(t(raw, "id")),
|
|
181
|
+
name: strOf(t(raw, "name"))
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
184
|
function providerOf(raw) {
|
|
184
|
-
const modelsArr =
|
|
185
|
+
const modelsArr = t(raw, "models");
|
|
185
186
|
const models = Array.isArray(modelsArr) ? modelsArr.map((item) => modelOf(item)) : [];
|
|
186
|
-
const apiKeyEnvRaw =
|
|
187
|
+
const apiKeyEnvRaw = t(raw, "apiKeyEnv");
|
|
187
188
|
return {
|
|
188
|
-
name: strOf(
|
|
189
|
-
displayName: strOf(
|
|
189
|
+
name: strOf(t(raw, "name")),
|
|
190
|
+
displayName: strOf(t(raw, "displayName")),
|
|
190
191
|
apiKeyEnv: typeof apiKeyEnvRaw === "string" ? apiKeyEnvRaw : null,
|
|
191
|
-
baseURL: strOf(
|
|
192
|
+
baseURL: strOf(t(raw, "baseURL")),
|
|
192
193
|
models,
|
|
193
|
-
hasKey:
|
|
194
|
+
hasKey: t(raw, "hasKey") === true
|
|
194
195
|
};
|
|
195
196
|
}
|
|
196
197
|
function providersPayloadOf(json) {
|
|
197
|
-
const providersArr =
|
|
198
|
+
const providersArr = t(json, "providers");
|
|
198
199
|
const providers = Array.isArray(providersArr) ? providersArr.map((item) => providerOf(item)) : [];
|
|
199
|
-
const currentRaw =
|
|
200
|
+
const currentRaw = t(json, "current");
|
|
200
201
|
const current = {
|
|
201
|
-
provider: strOf(
|
|
202
|
-
model: strOf(
|
|
203
|
-
keyIsDynamic:
|
|
204
|
-
url: strOf(
|
|
202
|
+
provider: strOf(t(currentRaw, "provider")),
|
|
203
|
+
model: strOf(t(currentRaw, "model")),
|
|
204
|
+
keyIsDynamic: t(currentRaw, "keyIsDynamic") === true,
|
|
205
|
+
url: strOf(t(currentRaw, "url"))
|
|
205
206
|
};
|
|
206
|
-
const sourceRaw =
|
|
207
|
+
const sourceRaw = t(json, "source");
|
|
207
208
|
return {
|
|
208
209
|
providers,
|
|
209
210
|
current,
|
|
210
|
-
getCredReady:
|
|
211
|
+
getCredReady: t(json, "getCredReady") === true,
|
|
211
212
|
source: {
|
|
212
|
-
status: strOf(
|
|
213
|
-
message: strOf(
|
|
213
|
+
status: strOf(t(sourceRaw, "status")),
|
|
214
|
+
message: strOf(t(sourceRaw, "message"))
|
|
214
215
|
},
|
|
215
|
-
csrf: strOf(
|
|
216
|
+
csrf: strOf(t(json, "csrf"))
|
|
216
217
|
};
|
|
217
218
|
}
|
|
218
219
|
/** 三行表单控件(数字/文本/下拉)共用的类名 = CARD_CSS 里 `.ocr-select{…}` 那条规则。
|
|
@@ -361,7 +362,7 @@ function OcrPluginCard(props) {
|
|
|
361
362
|
}
|
|
362
363
|
}, headRow, chevron);
|
|
363
364
|
const body = open ? (0, react.createElement)("div", { className: "ocr-card-body" }, children) : null;
|
|
364
|
-
return (0, react.createElement)("
|
|
365
|
+
return (0, react.createElement)("div", { className: `ocr-card${open ? " ocr-card-open" : ""}` }, header, body);
|
|
365
366
|
}
|
|
366
367
|
/** 整数数字输入行(保存条模式:合法输入上报草稿,不直写)。 */
|
|
367
368
|
function NumberRow(props) {
|
|
@@ -698,7 +699,7 @@ async function refreshProviders(sinks) {
|
|
|
698
699
|
try {
|
|
699
700
|
const res = await fetch(PROVIDERS_URL, { headers: { accept: "application/json" } });
|
|
700
701
|
if (!res.ok) {
|
|
701
|
-
const detail = strOf(
|
|
702
|
+
const detail = strOf(t(await res.json().catch(() => ({})), "error"));
|
|
702
703
|
throw new Error(detail === "" ? `HTTP ${res.status}` : detail);
|
|
703
704
|
}
|
|
704
705
|
const payload = providersPayloadOf(await res.json());
|
|
@@ -729,11 +730,11 @@ async function postWithCsrf(url, body, deps) {
|
|
|
729
730
|
}
|
|
730
731
|
/** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。 */
|
|
731
732
|
async function runApplySelection(deps) {
|
|
732
|
-
const { t, effectiveProvider, effectiveModel } = deps;
|
|
733
|
+
const { t: t$1, effectiveProvider, effectiveModel } = deps;
|
|
733
734
|
if (!effectiveProvider || !effectiveModel) {
|
|
734
735
|
deps.setActionMsg({
|
|
735
736
|
ok: false,
|
|
736
|
-
text: t("pickProviderModel")
|
|
737
|
+
text: t$1("pickProviderModel")
|
|
737
738
|
});
|
|
738
739
|
return;
|
|
739
740
|
}
|
|
@@ -744,14 +745,14 @@ async function runApplySelection(deps) {
|
|
|
744
745
|
provider: effectiveProvider,
|
|
745
746
|
model: effectiveModel
|
|
746
747
|
}))).json();
|
|
747
|
-
if (
|
|
748
|
+
if (t(selJson, "ok") === true) {
|
|
748
749
|
let testText;
|
|
749
|
-
const llmTestRaw =
|
|
750
|
-
if (
|
|
751
|
-
const testOutput = strOf(
|
|
752
|
-
testText =
|
|
753
|
-
} else testText = t("notVerified");
|
|
754
|
-
const appliedText = t("applied", {
|
|
750
|
+
const llmTestRaw = t(selJson, "llmTest");
|
|
751
|
+
if (e(llmTestRaw)) {
|
|
752
|
+
const testOutput = strOf(t(llmTestRaw, "output")).slice(0, 400);
|
|
753
|
+
testText = t(llmTestRaw, "ok") === true ? t$1("llmTestPassed") : `${t$1("llmTestFailed")}${testOutput}`;
|
|
754
|
+
} else testText = t$1("notVerified");
|
|
755
|
+
const appliedText = t$1("applied", {
|
|
755
756
|
provider: effectiveProvider,
|
|
756
757
|
model: effectiveModel,
|
|
757
758
|
test: testText
|
|
@@ -762,14 +763,14 @@ async function runApplySelection(deps) {
|
|
|
762
763
|
});
|
|
763
764
|
deps.refresh();
|
|
764
765
|
} else {
|
|
765
|
-
const failure = strOf(
|
|
766
|
+
const failure = strOf(t(selJson, "error"));
|
|
766
767
|
deps.setActionMsg({
|
|
767
768
|
ok: false,
|
|
768
|
-
text: failure === "" ? t("applyFailed") : failure
|
|
769
|
+
text: failure === "" ? t$1("applyFailed") : failure
|
|
769
770
|
});
|
|
770
771
|
}
|
|
771
772
|
} catch (error) {
|
|
772
|
-
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
773
|
+
const failedText = `${t$1("requestFailed")}${errorSummary(error)}`;
|
|
773
774
|
deps.setActionMsg({
|
|
774
775
|
ok: false,
|
|
775
776
|
text: failedText
|
|
@@ -780,24 +781,24 @@ async function runApplySelection(deps) {
|
|
|
780
781
|
}
|
|
781
782
|
/** POST migrate:明文 api_key 一键迁到 api_key_cmd,回执把迁移/跳过两个名单拼成一行。 */
|
|
782
783
|
async function runMigrateKeys(deps) {
|
|
783
|
-
const { t } = deps;
|
|
784
|
+
const { t: t$2 } = deps;
|
|
784
785
|
deps.setBusy(true);
|
|
785
786
|
deps.setActionMsg(null);
|
|
786
787
|
try {
|
|
787
788
|
const json = await (await deps.post(MIGRATE_URL)).json();
|
|
788
|
-
const migratedVal =
|
|
789
|
-
const skippedVal =
|
|
789
|
+
const migratedVal = t(json, "migrated");
|
|
790
|
+
const skippedVal = t(json, "skippedUnknown");
|
|
790
791
|
const migrated = Array.isArray(migratedVal) ? migratedVal.filter((item) => typeof item === "string") : [];
|
|
791
792
|
const skipped = Array.isArray(skippedVal) ? skippedVal.filter((item) => typeof item === "string") : [];
|
|
792
|
-
const ok =
|
|
793
|
-
const head = ok ? (migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey")) + (skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "") : t("migrateFailed");
|
|
793
|
+
const ok = t(json, "ok") === true;
|
|
794
|
+
const head = ok ? (migrated.length > 0 ? t$2("migratedPrefix") + migrated.join(", ") : t$2("noPlainKey")) + (skipped.length > 0 ? t$2("skippedPrefix") + skipped.join(", ") : "") : t$2("migrateFailed");
|
|
794
795
|
deps.setActionMsg({
|
|
795
796
|
ok,
|
|
796
797
|
text: head
|
|
797
798
|
});
|
|
798
799
|
deps.refresh();
|
|
799
800
|
} catch (error) {
|
|
800
|
-
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
801
|
+
const failedText = `${t$2("requestFailed")}${errorSummary(error)}`;
|
|
801
802
|
deps.setActionMsg({
|
|
802
803
|
ok: false,
|
|
803
804
|
text: failedText
|
|
@@ -808,19 +809,19 @@ async function runMigrateKeys(deps) {
|
|
|
808
809
|
}
|
|
809
810
|
/** POST test:手动触发 ocr llm test,输出原样回显在动作条上。 */
|
|
810
811
|
async function runTestConnection(deps) {
|
|
811
|
-
const { t } = deps;
|
|
812
|
+
const { t: t$3 } = deps;
|
|
812
813
|
deps.setBusy(true);
|
|
813
814
|
deps.setActionMsg(null);
|
|
814
815
|
try {
|
|
815
816
|
const json = await (await deps.post(TEST_URL)).json();
|
|
816
|
-
const ok =
|
|
817
|
-
const testOutput = strOf(
|
|
817
|
+
const ok = t(json, "ok") === true;
|
|
818
|
+
const testOutput = strOf(t(json, "output"));
|
|
818
819
|
deps.setActionMsg({
|
|
819
820
|
ok,
|
|
820
|
-
text: (ok ? t("llmTestPassed") : t("llmTestFailed")) + testOutput
|
|
821
|
+
text: (ok ? t$3("llmTestPassed") : t$3("llmTestFailed")) + testOutput
|
|
821
822
|
});
|
|
822
823
|
} catch (error) {
|
|
823
|
-
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
824
|
+
const failedText = `${t$3("requestFailed")}${errorSummary(error)}`;
|
|
824
825
|
deps.setActionMsg({
|
|
825
826
|
ok: false,
|
|
826
827
|
text: failedText
|
|
@@ -829,7 +830,10 @@ async function runTestConnection(deps) {
|
|
|
829
830
|
deps.setBusy(false);
|
|
830
831
|
}
|
|
831
832
|
}
|
|
832
|
-
/** 只写差异字段:touched 值为 undefined → 写 null(= 清回 schema 默认)。
|
|
833
|
+
/** 只写差异字段:touched 值为 undefined → 写 null(= 清回 schema 默认)。
|
|
834
|
+
* 受理位必须回来:`set` 回 `false` 表示宿主拒收或跳过了这次写入(值没落盘),
|
|
835
|
+
* 这时不能清 touched、也不能报成功——否则用户看到「已保存」而配置原封未动。
|
|
836
|
+
* 传输失败仍以 rejection 表达。 */
|
|
833
837
|
async function saveSettingsDraft(deps) {
|
|
834
838
|
const { t, value, touched } = deps;
|
|
835
839
|
const keys = diffTouched(touched, value);
|
|
@@ -838,11 +842,16 @@ async function saveSettingsDraft(deps) {
|
|
|
838
842
|
deps.setSaveError(null);
|
|
839
843
|
const ops = keys.map(async (field) => {
|
|
840
844
|
const draft = touched[field];
|
|
841
|
-
|
|
845
|
+
return deps.set(field, draft === void 0 ? null : draft);
|
|
842
846
|
});
|
|
843
847
|
try {
|
|
844
|
-
await Promise.all(ops);
|
|
848
|
+
const settled = await Promise.all(ops);
|
|
845
849
|
deps.setSettingsBusy(false);
|
|
850
|
+
const refused = keys.filter((_field, index) => settled[index] !== true);
|
|
851
|
+
if (refused.length > 0) {
|
|
852
|
+
deps.setSaveError(t("saveRefused", { fields: refused.join(", ") }));
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
846
855
|
deps.setTouched({});
|
|
847
856
|
} catch (error) {
|
|
848
857
|
deps.setSettingsBusy(false);
|
|
@@ -1014,13 +1023,7 @@ function apply(ctx) {
|
|
|
1014
1023
|
inject: () => ({
|
|
1015
1024
|
t,
|
|
1016
1025
|
hooks: { card: store },
|
|
1017
|
-
set:
|
|
1018
|
-
try {
|
|
1019
|
-
await scope.set(field, value);
|
|
1020
|
-
} catch (error) {
|
|
1021
|
-
console.error(`[ocr-review-card] set ${field} failed:`, error);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1026
|
+
set: (field, value) => scope.set(field, value)
|
|
1024
1027
|
})
|
|
1025
1028
|
}, OcrCard);
|
|
1026
1029
|
});
|
package/cordis.patch.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ocr-review
|
|
1
|
+
# ocr-review:open-code-review 工作区审查插件(bundle)。
|
|
2
2
|
# 双面包机制:本包含 Host 半(host.ts:5 个工具 ocr_review/ocr_scan/
|
|
3
3
|
# ocr_delegate_preview/ocr_delegate_rule/ocr_session + settings namespace「ocr-review」+
|
|
4
4
|
# webServer 配置端点)与 Client 半(src/client-entry.ts → client.js:设置卡片,
|