@jayyuen66/dsh-ocr-review 0.1.4 → 0.1.6
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 +86 -53
- package/README.zh-CN.md +84 -49
- package/THIRD_PARTY_NOTICES.md +10 -8
- package/client.js +693 -123
- package/cordis.patch.yml +6 -5
- package/host.js +985 -359
- package/package.json +49 -52
- package/scripts/get-cred.mjs +27 -14
package/client.js
CHANGED
|
@@ -20,7 +20,8 @@ const UI_MESSAGES = {
|
|
|
20
20
|
revert: "撤销",
|
|
21
21
|
saveFailed: "保存失败:",
|
|
22
22
|
saveRefused: "未写入(宿主拒收或跳过):{fields}",
|
|
23
|
-
loadProvidersFailed: "加载 providers 失败:{error}
|
|
23
|
+
loadProvidersFailed: "加载 providers 失败:{error}(远程面未就绪?重启 dsh 后重试)",
|
|
24
|
+
remoteUnavailable: "远程服务不可用:宿主没有挂上本插件的设置通道。",
|
|
24
25
|
loadingProviders: "加载 providers…",
|
|
25
26
|
noKeySuffix: "(未配置 key)",
|
|
26
27
|
noModel: "(无模型)",
|
|
@@ -36,6 +37,7 @@ const UI_MESSAGES = {
|
|
|
36
37
|
noProvider: "当前未配置 provider",
|
|
37
38
|
noReason: "宿主未给出原因(重启 dsh 或重试可恢复)",
|
|
38
39
|
readProvidersFailed: "未能从 dsh 配置服务读取 provider 列表:{reason}",
|
|
40
|
+
endpointVerifyFailed: "装载期端点自检失败(配置仍可修改):{reason}",
|
|
39
41
|
applied: "已应用:{provider} / {model}\n{test}",
|
|
40
42
|
notVerified: "(未自动验证,可点「测试连接」手动验证)",
|
|
41
43
|
llmTestPassed: "✓ ocr llm test 通过",
|
|
@@ -66,7 +68,30 @@ const UI_MESSAGES = {
|
|
|
66
68
|
configPathHint: "外部 open-code-review CLI 自己的配置位置;留空 = ~/.opencodereview/config.json。自定义时必须是 <X>/.opencodereview/config.json 布局(OCR 只认该形状,插件经 HOME 重定向让它生效)",
|
|
67
69
|
settingsLoading: "设置加载中,稍候可改",
|
|
68
70
|
keyDynamicHint: "key 走 get-cred.mjs 动态读取,OCR 配置不留明文",
|
|
69
|
-
keyScriptMissing: "⚠ get-cred 脚本缺失,api_key_cmd 无法工作"
|
|
71
|
+
keyScriptMissing: "⚠ get-cred 脚本缺失,api_key_cmd 无法工作",
|
|
72
|
+
binaryTitle: "外部 CLI(ocr)",
|
|
73
|
+
binaryUnknown: "状态未取到:刷新本页,或确认 dsh 的 web 服务已起来",
|
|
74
|
+
binaryBundled: "随本插件分发的兜底入口已就位:{path}({channel})",
|
|
75
|
+
binaryPath: "用 PATH 上的全局安装:{path}({channel})",
|
|
76
|
+
binaryNone: "未找到 ocr:PATH 与随包兜底两条路都没有可执行候选。装法与版本看下一行(由插件按核对版本给出)",
|
|
77
|
+
channelBrew: "brew formula",
|
|
78
|
+
channelNpm: "npm 全局包",
|
|
79
|
+
channelUnknown: "装法未识别",
|
|
80
|
+
versionExpected: "本插件的契约按 ocr {expected} 逐条核对",
|
|
81
|
+
versionMatched: "本机实测 {actual},与核对版本一致",
|
|
82
|
+
versionDiverged: "本机实测 {actual},与核对版本 {expected} 不一致:评论口径可能随版本漂移",
|
|
83
|
+
badgeDiverged: "ocr 版本不一致",
|
|
84
|
+
detectCli: "检测版本",
|
|
85
|
+
detecting: "检测中…",
|
|
86
|
+
probeVersion: "ocr 版本:{version}",
|
|
87
|
+
probeReason: "未取到版本号:{reason}",
|
|
88
|
+
badgeBundled: "ocr 走随包兜底",
|
|
89
|
+
badgePath: "ocr 来自 PATH",
|
|
90
|
+
badgeNone: "未找到 ocr",
|
|
91
|
+
badgeUnknown: "ocr 状态未取到",
|
|
92
|
+
binaryProbeHint: "版本号要在「配置」页点「检测版本」取——那要起一次 ocr 进程,不在本页顺手跑。",
|
|
93
|
+
binaryGuideDetails: "打开插件详情",
|
|
94
|
+
binaryGuideDismiss: "知道了"
|
|
70
95
|
},
|
|
71
96
|
en: {
|
|
72
97
|
cardTitle: "open-code-review code review",
|
|
@@ -79,7 +104,8 @@ const UI_MESSAGES = {
|
|
|
79
104
|
revert: "Revert",
|
|
80
105
|
saveFailed: "save failed: ",
|
|
81
106
|
saveRefused: "not written (host refused or skipped): {fields}",
|
|
82
|
-
loadProvidersFailed: "failed to load providers: {error} (
|
|
107
|
+
loadProvidersFailed: "failed to load providers: {error} (remote face not ready? restart dsh and retry)",
|
|
108
|
+
remoteUnavailable: "remote service unavailable: the host did not mount this plugin's settings channel",
|
|
83
109
|
loadingProviders: "loading providers…",
|
|
84
110
|
noKeySuffix: " (no key configured)",
|
|
85
111
|
noModel: " (no model)",
|
|
@@ -95,6 +121,7 @@ const UI_MESSAGES = {
|
|
|
95
121
|
noProvider: "No provider configured yet",
|
|
96
122
|
noReason: "the host gave no reason (restart dsh or retry to recover)",
|
|
97
123
|
readProvidersFailed: "Could not read the provider list from the dsh settings service: {reason}",
|
|
124
|
+
endpointVerifyFailed: "Startup endpoint check failed (the config is still editable): {reason}",
|
|
98
125
|
applied: "Applied: {provider} / {model}\n{test}",
|
|
99
126
|
notVerified: "(not verified automatically — use “Test connection” to check manually)",
|
|
100
127
|
llmTestPassed: "✓ ocr llm test passed",
|
|
@@ -125,16 +152,201 @@ const UI_MESSAGES = {
|
|
|
125
152
|
configPathHint: "Where the external open-code-review CLI keeps its own config; empty = ~/.opencodereview/config.json. A custom value must follow the <X>/.opencodereview/config.json layout (OCR only reads that shape; the plugin makes it effective via a HOME redirect)",
|
|
126
153
|
settingsLoading: "settings still loading — editable in a moment",
|
|
127
154
|
keyDynamicHint: "Keys are read dynamically via get-cred.mjs; OCR config keeps no plain text",
|
|
128
|
-
keyScriptMissing: "⚠ get-cred script missing, api_key_cmd cannot work"
|
|
155
|
+
keyScriptMissing: "⚠ get-cred script missing, api_key_cmd cannot work",
|
|
156
|
+
binaryTitle: "External CLI (ocr)",
|
|
157
|
+
binaryUnknown: "status not reported: reload this page, or check that the dsh web server is up",
|
|
158
|
+
binaryBundled: "bundled fallback entry in place: {path} ({channel})",
|
|
159
|
+
binaryPath: "using the global install on PATH: {path} ({channel})",
|
|
160
|
+
binaryNone: "ocr not found: neither PATH nor the bundled fallback has an executable candidate. The install command and version are on the next line (this plugin states the version its contract was verified against)",
|
|
161
|
+
channelBrew: "brew formula",
|
|
162
|
+
channelNpm: "npm global package",
|
|
163
|
+
channelUnknown: "install method unrecognised",
|
|
164
|
+
versionExpected: "this plugin's contract was verified against ocr {expected}",
|
|
165
|
+
versionMatched: "this machine reports {actual}, matching it",
|
|
166
|
+
versionDiverged: "this machine reports {actual}, which is not the verified {expected}: comment wording and severity may drift between versions",
|
|
167
|
+
badgeDiverged: "ocr version differs",
|
|
168
|
+
detectCli: "Detect version",
|
|
169
|
+
detecting: "Detecting...",
|
|
170
|
+
probeVersion: "ocr version: {version}",
|
|
171
|
+
probeReason: "no version read: {reason}",
|
|
172
|
+
badgeBundled: "ocr from bundled fallback",
|
|
173
|
+
badgePath: "ocr from PATH",
|
|
174
|
+
badgeNone: "ocr not found",
|
|
175
|
+
badgeUnknown: "ocr status unavailable",
|
|
176
|
+
binaryProbeHint: "the version comes from the Detect action on the configuration page — that spawns an ocr process, so it is not run here",
|
|
177
|
+
binaryGuideDetails: "Open plugin details",
|
|
178
|
+
binaryGuideDismiss: "Got it"
|
|
129
179
|
}
|
|
130
180
|
};
|
|
131
181
|
//#endregion
|
|
132
|
-
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.
|
|
133
|
-
function e(e) {
|
|
182
|
+
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.6_@deepseek-ai+cordis@4.0.5_@deepseek-ai+dsh-client-lo_8c875f5679b30ed9bd8ed9c09e6b1478/node_modules/@jayyuen66/dsh-plugin-shared/dist/record.js
|
|
183
|
+
function e$2(e) {
|
|
134
184
|
return typeof e == `object` && !!e && !Array.isArray(e);
|
|
135
185
|
}
|
|
136
|
-
function
|
|
137
|
-
return e(t) ? t[n] : void 0;
|
|
186
|
+
function n(t, n) {
|
|
187
|
+
return e$2(t) ? t[n] : void 0;
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.6_@deepseek-ai+cordis@4.0.5_@deepseek-ai+dsh-client-lo_8c875f5679b30ed9bd8ed9c09e6b1478/node_modules/@jayyuen66/dsh-plugin-shared/dist/card-apply.js
|
|
191
|
+
function e$1(e, t, n) {
|
|
192
|
+
let r = globalThis;
|
|
193
|
+
if (Object.hasOwn(r, t) && r[t] === !0) return !1;
|
|
194
|
+
r[t] = !0;
|
|
195
|
+
try {
|
|
196
|
+
e.effect(() => () => {
|
|
197
|
+
r[t] = void 0;
|
|
198
|
+
}, n);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
throw r[t] = void 0, e;
|
|
201
|
+
}
|
|
202
|
+
return !0;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region node_modules/.pnpm/@jayyuen66+dsh-plugin-shared@0.1.6_@deepseek-ai+cordis@4.0.5_@deepseek-ai+dsh-client-lo_8c875f5679b30ed9bd8ed9c09e6b1478/node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
|
|
206
|
+
function e(e, t) {
|
|
207
|
+
let n = [];
|
|
208
|
+
for (let r of Object.keys(e)) JSON.stringify(e[r] ?? null) !== JSON.stringify(t[r] ?? null) && n.push(r);
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region lib/remote-shape.ts
|
|
213
|
+
/** 宿主服务键 = 客户端线上命名空间(两侧共用的唯一来源)。 */
|
|
214
|
+
const OCR_REMOTE_NAMESPACE = "ocrReview";
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/remote.ts
|
|
217
|
+
/** 网关给命名空间注册的**服务键**:`remote.<namespace>`。它在 `$mount` 完成之后才存在,
|
|
218
|
+
* 所以只能在挂载后按这个键去读,而不是提前 inject。 */
|
|
219
|
+
const OCR_SERVICE_KEY = `remote.${OCR_REMOTE_NAMESPACE}`;
|
|
220
|
+
/** 卡片所在 bundle 的包名(`$mount` 的 `package` 位与槽位键同源,见 client-entry.ts 的 BUNDLE_PKG)。 */
|
|
221
|
+
const PACKAGE_NAME = "@jayyuen66/dsh-ocr-review";
|
|
222
|
+
/** 把宿主那五个方法挂到客户端远程面上的描述符(`id` 的形状照生成器:`<包>#<命名空间>/<方法>`)。
|
|
223
|
+
* 导出它只为一件事:`test/remote.test.ts` 拿它与宿主侧 `remoteMethods()` 的标记对质
|
|
224
|
+
* —— 两侧分叉时卡片不会报错,只会"读不到",所以那条比较必须在门禁里。 */
|
|
225
|
+
const OCR_DESCRIPTORS = [
|
|
226
|
+
{
|
|
227
|
+
id: `${PACKAGE_NAME}#${OCR_REMOTE_NAMESPACE}/providers`,
|
|
228
|
+
service: OCR_REMOTE_NAMESPACE,
|
|
229
|
+
namespace: OCR_REMOTE_NAMESPACE,
|
|
230
|
+
method: "providers",
|
|
231
|
+
invocation: { kind: "direct" },
|
|
232
|
+
parameters: [],
|
|
233
|
+
result: { mode: "src-json" }
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: `${PACKAGE_NAME}#${OCR_REMOTE_NAMESPACE}/select`,
|
|
237
|
+
service: OCR_REMOTE_NAMESPACE,
|
|
238
|
+
namespace: OCR_REMOTE_NAMESPACE,
|
|
239
|
+
method: "select",
|
|
240
|
+
invocation: { kind: "direct" },
|
|
241
|
+
parameters: [{
|
|
242
|
+
name: "request",
|
|
243
|
+
wire: "request",
|
|
244
|
+
source: "json",
|
|
245
|
+
codec: {
|
|
246
|
+
mode: "strict",
|
|
247
|
+
typeSymbol: `${PACKAGE_NAME}#select-request`,
|
|
248
|
+
create: () => ({ parse(value) {
|
|
249
|
+
if (!e$2(value)) throw new TypeError("select request must be an object");
|
|
250
|
+
const { provider, model } = value;
|
|
251
|
+
if (typeof provider !== "string" || typeof model !== "string") throw new TypeError("select request requires string provider and model");
|
|
252
|
+
return {
|
|
253
|
+
provider,
|
|
254
|
+
model
|
|
255
|
+
};
|
|
256
|
+
} })
|
|
257
|
+
}
|
|
258
|
+
}],
|
|
259
|
+
result: { mode: "src-json" }
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: `${PACKAGE_NAME}#${OCR_REMOTE_NAMESPACE}/migrate`,
|
|
263
|
+
service: OCR_REMOTE_NAMESPACE,
|
|
264
|
+
namespace: OCR_REMOTE_NAMESPACE,
|
|
265
|
+
method: "migrate",
|
|
266
|
+
invocation: { kind: "direct" },
|
|
267
|
+
parameters: [],
|
|
268
|
+
result: { mode: "src-json" }
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: `${PACKAGE_NAME}#${OCR_REMOTE_NAMESPACE}/test`,
|
|
272
|
+
service: OCR_REMOTE_NAMESPACE,
|
|
273
|
+
namespace: OCR_REMOTE_NAMESPACE,
|
|
274
|
+
method: "test",
|
|
275
|
+
invocation: { kind: "direct" },
|
|
276
|
+
parameters: [],
|
|
277
|
+
result: { mode: "src-json" }
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: `${PACKAGE_NAME}#${OCR_REMOTE_NAMESPACE}/probe`,
|
|
281
|
+
service: OCR_REMOTE_NAMESPACE,
|
|
282
|
+
namespace: OCR_REMOTE_NAMESPACE,
|
|
283
|
+
method: "probe",
|
|
284
|
+
invocation: { kind: "direct" },
|
|
285
|
+
parameters: [],
|
|
286
|
+
result: { mode: "src-json" }
|
|
287
|
+
}
|
|
288
|
+
];
|
|
289
|
+
/** 这个值能当远程面用吗(只核我们要调的那一位;服务来自别的实现或版本时保守当作没有)。 */
|
|
290
|
+
const isRemoteMounter = (value) => e$2(value) && typeof value["$mount"] === "function";
|
|
291
|
+
/** 同上:命名空间服务(挂载之后才有,缺它说明网关没认这枚贡献)。 */
|
|
292
|
+
const isOcrNamespace = (value) => e$2(value) && typeof value["providers"] === "function" && typeof value["select"] === "function" && typeof value["migrate"] === "function" && typeof value["test"] === "function" && typeof value["probe"] === "function";
|
|
293
|
+
/** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。 */
|
|
294
|
+
function unwrap(result) {
|
|
295
|
+
if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
296
|
+
return result.value;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 造出真正的读取器(挂载一次 + 每次读命名空间服务)。
|
|
300
|
+
* @param ctx - 客户端上下文
|
|
301
|
+
* @param remote - 已核过形状的远程面
|
|
302
|
+
* @returns 读取器
|
|
303
|
+
*/
|
|
304
|
+
function buildOcrReader(ctx, remote) {
|
|
305
|
+
let mounted;
|
|
306
|
+
const namespaceOf = async () => {
|
|
307
|
+
mounted ??= remote.$mount({
|
|
308
|
+
package: PACKAGE_NAME,
|
|
309
|
+
descriptors: OCR_DESCRIPTORS
|
|
310
|
+
});
|
|
311
|
+
await mounted;
|
|
312
|
+
const namespace = ctx.get?.(OCR_SERVICE_KEY, false);
|
|
313
|
+
if (!isOcrNamespace(namespace)) throw new Error(`${OCR_SERVICE_KEY} 未提供:宿主网关没有挂上这枚命名空间`);
|
|
314
|
+
return namespace;
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
async providers() {
|
|
318
|
+
return unwrap(await (await namespaceOf()).providers());
|
|
319
|
+
},
|
|
320
|
+
async select(provider, model) {
|
|
321
|
+
return unwrap(await (await namespaceOf()).select({
|
|
322
|
+
provider,
|
|
323
|
+
model
|
|
324
|
+
}));
|
|
325
|
+
},
|
|
326
|
+
async migrate() {
|
|
327
|
+
return unwrap(await (await namespaceOf()).migrate());
|
|
328
|
+
},
|
|
329
|
+
async test() {
|
|
330
|
+
return unwrap(await (await namespaceOf()).test());
|
|
331
|
+
},
|
|
332
|
+
async probe() {
|
|
333
|
+
return unwrap(await (await namespaceOf()).probe());
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 造一个「读/写宿主侧 OCR 配置」的读取器;**当前部署没有远程面时返回 `undefined`**
|
|
339
|
+
* (卡片照常渲染,只是数据面显示读不到 —— 与 host 侧可选读面同一条口径)。
|
|
340
|
+
*
|
|
341
|
+
* `$mount` 是**幂等**的(挂进本 fiber,随卡片卸载自动撤),所以这里缓存那枚 promise:
|
|
342
|
+
* 同一次会话里反复拉取只挂一次。
|
|
343
|
+
*
|
|
344
|
+
* @param ctx - 客户端上下文(只用 `get` 一位)
|
|
345
|
+
* @returns 读取器;没有远程面时 `undefined`
|
|
346
|
+
*/
|
|
347
|
+
function createOcrReader(ctx) {
|
|
348
|
+
const remote = ctx.get?.("remote");
|
|
349
|
+
return isRemoteMounter(remote) ? buildOcrReader(ctx, remote) : void 0;
|
|
138
350
|
}
|
|
139
351
|
//#endregion
|
|
140
352
|
//#region src/client-entry.ts
|
|
@@ -157,12 +369,6 @@ const NS = "ocr-review";
|
|
|
157
369
|
* dsh-client-ui-settings/lib/client.js:1309-1315 把入参原样当 settings 命名空间用)。
|
|
158
370
|
*/
|
|
159
371
|
const BUNDLE_PKG = "@jayyuen66/dsh-ocr-review";
|
|
160
|
-
const PROVIDERS_URL = "/_dsh/ocr-review/providers";
|
|
161
|
-
const SELECT_URL = "/_dsh/ocr-review/select";
|
|
162
|
-
const MIGRATE_URL = "/_dsh/ocr-review/migrate";
|
|
163
|
-
const TEST_URL = "/_dsh/ocr-review/test";
|
|
164
|
-
/** 写端点 CSRF 头名(与 host.ts OCR_CSRF_HEADER 同源,改名要两端一起改)。 */
|
|
165
|
-
const CSRF_HEADER = "x-ocr-csrf";
|
|
166
372
|
/** 未知值 → 安全字符串(非字符串给 '')。 */
|
|
167
373
|
function strOf(value) {
|
|
168
374
|
return typeof value === "string" ? value : "";
|
|
@@ -177,49 +383,102 @@ function errorSummary(error) {
|
|
|
177
383
|
}
|
|
178
384
|
function modelOf(raw) {
|
|
179
385
|
return {
|
|
180
|
-
id: strOf(
|
|
181
|
-
name: strOf(
|
|
386
|
+
id: strOf(n(raw, "id")),
|
|
387
|
+
name: strOf(n(raw, "name"))
|
|
182
388
|
};
|
|
183
389
|
}
|
|
184
390
|
function providerOf(raw) {
|
|
185
|
-
const modelsArr =
|
|
391
|
+
const modelsArr = n(raw, "models");
|
|
186
392
|
const models = Array.isArray(modelsArr) ? modelsArr.map((item) => modelOf(item)) : [];
|
|
187
|
-
const apiKeyEnvRaw =
|
|
393
|
+
const apiKeyEnvRaw = n(raw, "apiKeyEnv");
|
|
188
394
|
return {
|
|
189
|
-
name: strOf(
|
|
190
|
-
displayName: strOf(
|
|
395
|
+
name: strOf(n(raw, "name")),
|
|
396
|
+
displayName: strOf(n(raw, "displayName")),
|
|
191
397
|
apiKeyEnv: typeof apiKeyEnvRaw === "string" ? apiKeyEnvRaw : null,
|
|
192
|
-
baseURL: strOf(
|
|
398
|
+
baseURL: strOf(n(raw, "baseURL")),
|
|
193
399
|
models,
|
|
194
|
-
hasKey:
|
|
400
|
+
hasKey: n(raw, "hasKey") === true
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** 可得性来源的合法域(与 host 侧 `OcrBinarySource` 同一份取值)。形状不合即「没报」。 */
|
|
404
|
+
const BINARY_SOURCES = /* @__PURE__ */ new Set([
|
|
405
|
+
"bundled",
|
|
406
|
+
"path",
|
|
407
|
+
"none"
|
|
408
|
+
]);
|
|
409
|
+
/**
|
|
410
|
+
* 解析宿主送的可得性对象。导出是为了让「wire 键名」这条契约能被钉住:卡片与 host
|
|
411
|
+
* 两处各写一份键名时,写错了的症状是状态行永远显示「未取到」,而不是任何报错。
|
|
412
|
+
*
|
|
413
|
+
* 只有 `source` 是必需的:其余四样各管一句附加信息,缺了就少说那一句,而不是把整行判定
|
|
414
|
+
* 作废——「宿主报了 PATH 却没报版本」仍然是一条用户要看的事实。`measuredVersion` 例外,
|
|
415
|
+
* 它恒在(null = 还没测过),因为「没测过」与「旧宿主不带这字段」在卡片上是同一句话。
|
|
416
|
+
*/
|
|
417
|
+
function binaryOf(raw) {
|
|
418
|
+
if (!e$2(raw)) return null;
|
|
419
|
+
const source = strOf(raw["source"]);
|
|
420
|
+
if (!BINARY_SOURCES.has(source)) return null;
|
|
421
|
+
const path = strOf(raw["path"]);
|
|
422
|
+
const channel = strOf(raw["channel"]);
|
|
423
|
+
const expectedVersion = strOf(raw["expectedVersion"]);
|
|
424
|
+
const installHint = strOf(raw["installHint"]);
|
|
425
|
+
const measured = raw["measuredVersion"];
|
|
426
|
+
return {
|
|
427
|
+
source,
|
|
428
|
+
...path === "" ? {} : { path },
|
|
429
|
+
...channel === "" ? {} : { channel },
|
|
430
|
+
...expectedVersion === "" ? {} : { expectedVersion },
|
|
431
|
+
...installHint === "" ? {} : { installHint },
|
|
432
|
+
measuredVersion: typeof measured === "string" ? measured : null
|
|
195
433
|
};
|
|
196
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* 详情页徽标/区块/启用指引共用的取数口:读同一条 providers 载荷的 `ocrBinary` 那半。
|
|
437
|
+
* 判据只在 host 的 `resolveOcrAvailability` 一处,本包三个显示面都只翻译它的结论。
|
|
438
|
+
* 读取器缺席(部署没装远程面)与传输失败都归成 null:这三处只负责显示。
|
|
439
|
+
*/
|
|
440
|
+
async function loadCliStatus(reader) {
|
|
441
|
+
if (reader === void 0) return null;
|
|
442
|
+
try {
|
|
443
|
+
return binaryOf(n(await reader.providers(), "ocrBinary"));
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
197
448
|
function providersPayloadOf(json) {
|
|
198
|
-
const providersArr =
|
|
449
|
+
const providersArr = n(json, "providers");
|
|
199
450
|
const providers = Array.isArray(providersArr) ? providersArr.map((item) => providerOf(item)) : [];
|
|
200
|
-
const currentRaw =
|
|
451
|
+
const currentRaw = n(json, "current");
|
|
201
452
|
const current = {
|
|
202
|
-
provider: strOf(
|
|
203
|
-
model: strOf(
|
|
204
|
-
keyIsDynamic:
|
|
205
|
-
url: strOf(
|
|
453
|
+
provider: strOf(n(currentRaw, "provider")),
|
|
454
|
+
model: strOf(n(currentRaw, "model")),
|
|
455
|
+
keyIsDynamic: n(currentRaw, "keyIsDynamic") === true,
|
|
456
|
+
url: strOf(n(currentRaw, "url"))
|
|
457
|
+
};
|
|
458
|
+
const sourceRaw = n(json, "source");
|
|
459
|
+
const verifyRaw = n(json, "endpointVerify");
|
|
460
|
+
const endpointVerify = verifyRaw === null || verifyRaw === void 0 ? null : {
|
|
461
|
+
ok: n(verifyRaw, "ok") === true,
|
|
462
|
+
message: strOf(n(verifyRaw, "message"))
|
|
206
463
|
};
|
|
207
|
-
const sourceRaw = t(json, "source");
|
|
208
464
|
return {
|
|
209
465
|
providers,
|
|
210
466
|
current,
|
|
211
|
-
getCredReady:
|
|
467
|
+
getCredReady: n(json, "getCredReady") === true,
|
|
212
468
|
source: {
|
|
213
|
-
status: strOf(
|
|
214
|
-
message: strOf(
|
|
469
|
+
status: strOf(n(sourceRaw, "status")),
|
|
470
|
+
message: strOf(n(sourceRaw, "message"))
|
|
215
471
|
},
|
|
216
|
-
|
|
472
|
+
endpointVerify,
|
|
473
|
+
ocrBinary: binaryOf(n(json, "ocrBinary"))
|
|
217
474
|
};
|
|
218
475
|
}
|
|
219
476
|
/** 三行表单控件(数字/文本/下拉)共用的类名 = CARD_CSS 里 `.ocr-select{…}` 那条规则。
|
|
220
477
|
* 抽出来是因为两处 input 与一处 select 必须同宽同边框:写三遍字面量时改一处漏两处,
|
|
221
478
|
* 症状是设置卡里某一行的控件比其它行窄。 */
|
|
222
479
|
const FORM_CONTROL_CLASS = "ocr-select";
|
|
480
|
+
/** 小节标题的类名(卡片头、CLI 状态行、详情页区块共用;sonarjs 的重复字面量门禁)。 */
|
|
481
|
+
const CARD_NAME_CLASS = "ocr-card-name";
|
|
223
482
|
const CARD_CSS = [
|
|
224
483
|
".ocr-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}",
|
|
225
484
|
".ocr-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
@@ -269,12 +528,6 @@ function cardStore(scope) {
|
|
|
269
528
|
}
|
|
270
529
|
};
|
|
271
530
|
}
|
|
272
|
-
/** touched 层与快照的差异字段(值语义比较;undefined 与缺失等价)。 */
|
|
273
|
-
function diffTouched(touched, value) {
|
|
274
|
-
const out = [];
|
|
275
|
-
for (const key of Object.keys(touched)) if (JSON.stringify(touched[key] ?? null) !== JSON.stringify(value[key] ?? null)) out.push(key);
|
|
276
|
-
return out;
|
|
277
|
-
}
|
|
278
531
|
function SaveBar(props) {
|
|
279
532
|
const { t } = props;
|
|
280
533
|
const disabled = !props.writable || props.busy;
|
|
@@ -338,7 +591,7 @@ function OcrPluginCard(props) {
|
|
|
338
591
|
const { title, description, children } = props;
|
|
339
592
|
const [open, setOpen] = (0, react.useState)(false);
|
|
340
593
|
const pathKey = "d";
|
|
341
|
-
const headRow = (0, react.createElement)("span", { className: "ocr-card-head" }, (0, react.createElement)("span", { className:
|
|
594
|
+
const headRow = (0, react.createElement)("span", { className: "ocr-card-head" }, (0, react.createElement)("span", { className: CARD_NAME_CLASS }, title), (0, react.createElement)("span", { className: "ocr-card-desc" }, description));
|
|
342
595
|
const chevron = (0, react.createElement)("svg", {
|
|
343
596
|
width: 14,
|
|
344
597
|
height: 14,
|
|
@@ -469,6 +722,225 @@ function currentConfigText(t, current) {
|
|
|
469
722
|
keyPart: t(current.keyIsDynamic ? "dynamicKey" : "plainKeyWarning")
|
|
470
723
|
});
|
|
471
724
|
}
|
|
725
|
+
/** 装法枚举到文案键:查表而不是嵌套三元(本仓 lint 禁嵌套三元),查不到就是「未识别」那一档。 */
|
|
726
|
+
const CHANNEL_KEYS = {
|
|
727
|
+
brew: "channelBrew",
|
|
728
|
+
npm: "channelNpm"
|
|
729
|
+
};
|
|
730
|
+
/** 那份 CLI 是怎么装上的,翻成人话。判不出来源(含 host 没报这一档)不猜。 */
|
|
731
|
+
function channelText(t, channel) {
|
|
732
|
+
return t(CHANNEL_KEYS[channel ?? ""] ?? "channelUnknown");
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* 外部 CLI 可得性那一行的文本。三档来源与「宿主没报」四种说法,判据全在 host 侧
|
|
736
|
+
* (`lib/cli.ts` 的 resolveOcrAvailability),卡片只翻译结论——两处各算各的就会漂。
|
|
737
|
+
*/
|
|
738
|
+
function binaryStatusText(t, binary) {
|
|
739
|
+
if (binary === null) return t("binaryUnknown");
|
|
740
|
+
if (binary.source === "bundled") return t("binaryBundled", {
|
|
741
|
+
path: binary.path ?? "",
|
|
742
|
+
channel: channelText(t, binary.channel)
|
|
743
|
+
});
|
|
744
|
+
if (binary.source === "path") return t("binaryPath", {
|
|
745
|
+
path: binary.path ?? "",
|
|
746
|
+
channel: channelText(t, binary.channel)
|
|
747
|
+
});
|
|
748
|
+
return t("binaryNone");
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* 版本核对那一行:「插件按哪一版核对」恒说,「本机是哪一版」只在测过之后才说。
|
|
752
|
+
* 没测过时不编造任何结论,也不在这里重复「点检测」——那句在按钮本身与区块指路里已经有了。
|
|
753
|
+
*/
|
|
754
|
+
function cliVersionText(t, binary) {
|
|
755
|
+
if (binary?.expectedVersion === void 0) return "";
|
|
756
|
+
const expected = binary.expectedVersion;
|
|
757
|
+
const lines = [t("versionExpected", { expected })];
|
|
758
|
+
if (binary.measuredVersion !== null) lines.push(binary.measuredVersion === expected ? t("versionMatched", { actual: binary.measuredVersion }) : t("versionDiverged", {
|
|
759
|
+
actual: binary.measuredVersion,
|
|
760
|
+
expected
|
|
761
|
+
}));
|
|
762
|
+
return lines.join(" · ");
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* 装法那一行:只在两条路都没有候选时出现。已装着的时候这条是噪音——那种情况下用户要的是
|
|
766
|
+
* 「哪一份、什么版本、要不要换」,那两行已经说了。
|
|
767
|
+
*/
|
|
768
|
+
function cliInstallText(binary) {
|
|
769
|
+
if (binary?.source !== "none") return "";
|
|
770
|
+
return binary.installHint ?? "";
|
|
771
|
+
}
|
|
772
|
+
/** 一条错误提示行的类名(多处在用;sonarjs 的重复字面量门禁要求收成常量)。 */
|
|
773
|
+
const STATUS_ERROR_CLASS = "ocr-status ocr-err";
|
|
774
|
+
/** 一条成功/失败回执行的类名(动作条、探测回执共用)。 */
|
|
775
|
+
function statusClass(ok) {
|
|
776
|
+
return `ocr-status ${ok ? "ocr-ok" : "ocr-err"}`;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* 外部 CLI 状态行:可得性 + 「检测」按钮 + 最近一次探测回执。
|
|
780
|
+
*
|
|
781
|
+
* 刻意画在 provider 表单**之外**(provider 源不可用时也要看得见)——「这台机器有没有
|
|
782
|
+
* ocr」与「宿主能不能列出 provider」是两件事,混在一支里会让缺 CLI 的机器连原因都看不到。
|
|
783
|
+
* 回执用 `.ocr-status`(`white-space:pre-wrap`)而不是输入框:那段文本里就有上游给的
|
|
784
|
+
* `npm i -g …` 命令,要能选中复制,而本包不提供剪贴板 API(非安全上下文会静默失败)。
|
|
785
|
+
*/
|
|
786
|
+
function buildCliStatusRow(deps) {
|
|
787
|
+
const { t } = deps;
|
|
788
|
+
const label = (0, react.createElement)("div", { className: CARD_NAME_CLASS }, t("binaryTitle"));
|
|
789
|
+
const button = (0, react.createElement)("button", {
|
|
790
|
+
className: "ocr-btn",
|
|
791
|
+
type: "button",
|
|
792
|
+
disabled: deps.busy !== null,
|
|
793
|
+
onClick: deps.onProbe,
|
|
794
|
+
"data-field": "probe"
|
|
795
|
+
}, deps.busy === "probe" ? t("detecting") : t("detectCli"));
|
|
796
|
+
const row = (0, react.createElement)("div", {
|
|
797
|
+
className: "ocr-row",
|
|
798
|
+
style: {
|
|
799
|
+
justifyContent: "flex-start",
|
|
800
|
+
gap: 8
|
|
801
|
+
}
|
|
802
|
+
}, label, button);
|
|
803
|
+
const statusText = binaryStatusText(t, deps.binary);
|
|
804
|
+
const status = (0, react.createElement)("div", { className: "ocr-hint" }, statusText);
|
|
805
|
+
const versionText = cliVersionText(t, deps.binary);
|
|
806
|
+
const versionLine = versionText === "" ? null : (0, react.createElement)("div", { className: "ocr-hint" }, versionText);
|
|
807
|
+
const hintText = cliInstallText(deps.binary);
|
|
808
|
+
const installLine = hintText === "" ? null : (0, react.createElement)("div", { className: "ocr-hint" }, hintText);
|
|
809
|
+
const probe = deps.probeMsg ? (0, react.createElement)("div", {
|
|
810
|
+
className: statusClass(deps.probeMsg.ok),
|
|
811
|
+
"data-field": "probe-result"
|
|
812
|
+
}, deps.probeMsg.text) : null;
|
|
813
|
+
return (0, react.createElement)("div", {
|
|
814
|
+
style: { padding: "2px 0" },
|
|
815
|
+
"data-field": "cli-status"
|
|
816
|
+
}, row, status, versionLine, installLine, probe);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* 徽标用的短文案(标题旁那只标签放不下整句装法)。与 `binaryStatusText` 同一判据、
|
|
820
|
+
* 不同长度:两处各算一份「哪一档」就会漂,所以入参仍是 host 送的那份 `OcrBinaryView`。
|
|
821
|
+
*
|
|
822
|
+
* 「实测过且与核对版本不一致」压过来源那一档:用户扫一眼列表要看到的结论是「这份 CLI
|
|
823
|
+
* 与本插件不同版本」,来源在哪一行里已经写了。没实测过就不压——那时不一致与否无从谈起。
|
|
824
|
+
*/
|
|
825
|
+
function cliBadgeText(t, binary) {
|
|
826
|
+
if (binary === null) return t("badgeUnknown");
|
|
827
|
+
if (binary.expectedVersion !== void 0 && binary.measuredVersion !== null && binary.measuredVersion !== binary.expectedVersion) return t("badgeDiverged");
|
|
828
|
+
if (binary.source === "bundled") return t("badgeBundled");
|
|
829
|
+
if (binary.source === "path") return t("badgePath");
|
|
830
|
+
return t("badgeNone");
|
|
831
|
+
}
|
|
832
|
+
/** 这页说的是不是本包(bundle 页与它的行页都算;官方插件页 `item` 没有包名可比)。
|
|
833
|
+
* 导出是因为「在别人插件的页上白拉一次自己的端点」这条判据只能在这里钉。 */
|
|
834
|
+
function isOwnSubject(subject) {
|
|
835
|
+
return subject.kind !== "item" && subject.pkg.name === BUNDLE_PKG;
|
|
836
|
+
}
|
|
837
|
+
/** 徽标那枚元素(状态已到才画;取状态与 subject 过滤留给组件,用例才不必起渲染器)。 */
|
|
838
|
+
function buildCliBadge(t, status) {
|
|
839
|
+
return (0, react.createElement)("span", {
|
|
840
|
+
className: "ocr-hint",
|
|
841
|
+
"data-field": "cli-badge"
|
|
842
|
+
}, cliBadgeText(t, status));
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* 订阅一次外部 CLI 可得性(GET providers 的 `ocrBinary` 那半,纯 fs 判据、不起进程)。
|
|
846
|
+
*
|
|
847
|
+
* `active` 为假(这页说的不是本包)时既不发请求也不画东西:官方契约是「对无话可说的
|
|
848
|
+
* subject 渲染 null」,在别人插件的页面上拉一次自己的端点属于白干的活。
|
|
849
|
+
* @returns undefined = 还没回来(渲染 null,不闪「状态未知」);null = 宿主没报。
|
|
850
|
+
*/
|
|
851
|
+
function useCliStatus(active, load) {
|
|
852
|
+
const [status, setStatus] = (0, react.useState)(void 0);
|
|
853
|
+
(0, react.useEffect)(() => {
|
|
854
|
+
let alive = true;
|
|
855
|
+
const settle = (value) => {
|
|
856
|
+
if (alive) setStatus(value);
|
|
857
|
+
};
|
|
858
|
+
const run = async () => {
|
|
859
|
+
try {
|
|
860
|
+
settle(await load());
|
|
861
|
+
} catch {
|
|
862
|
+
settle(null);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
if (active) run();
|
|
866
|
+
return () => {
|
|
867
|
+
alive = false;
|
|
868
|
+
};
|
|
869
|
+
}, [active, load]);
|
|
870
|
+
return status;
|
|
871
|
+
}
|
|
872
|
+
/** 详情页标题旁的徽标:一句话说明外部 ocr 从哪条路来(或压根没有)。 */
|
|
873
|
+
function OcrCliBadge(props) {
|
|
874
|
+
const mine = isOwnSubject(props.subject);
|
|
875
|
+
const status = useCliStatus(mine, props.loadCliStatus);
|
|
876
|
+
if (!mine || status === void 0) return null;
|
|
877
|
+
return buildCliBadge(props.t, status);
|
|
878
|
+
}
|
|
879
|
+
/** 区块体:标题 + 完整状态那一行 + 版本核对那一行 + 装法(没装时)+ 指路那一句。 */
|
|
880
|
+
function buildCliSection(t, status) {
|
|
881
|
+
const title = (0, react.createElement)("div", { className: CARD_NAME_CLASS }, t("binaryTitle"));
|
|
882
|
+
const body = (0, react.createElement)("div", { className: "ocr-hint" }, binaryStatusText(t, status));
|
|
883
|
+
const versionText = cliVersionText(t, status);
|
|
884
|
+
const versionLine = versionText === "" ? null : (0, react.createElement)("div", { className: "ocr-hint" }, versionText);
|
|
885
|
+
const hintText = cliInstallText(status);
|
|
886
|
+
const installLine = hintText === "" ? null : (0, react.createElement)("div", { className: "ocr-hint" }, hintText);
|
|
887
|
+
const hint = (0, react.createElement)("div", { className: "ocr-hint" }, t("binaryProbeHint"));
|
|
888
|
+
return (0, react.createElement)("div", {
|
|
889
|
+
style: { padding: "8px 0" },
|
|
890
|
+
"data-field": "cli-section"
|
|
891
|
+
}, title, body, versionLine, installLine, hint);
|
|
892
|
+
}
|
|
893
|
+
/** 详情页下方的区块:完整状态 + 「版本号要在配置卡片里点检测」的指路。 */
|
|
894
|
+
function OcrCliSection(props) {
|
|
895
|
+
const mine = isOwnSubject(props.subject);
|
|
896
|
+
const status = useCliStatus(mine, props.loadCliStatus);
|
|
897
|
+
if (!mine || status === void 0) return null;
|
|
898
|
+
return buildCliSection(props.t, status);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* 启用指引的内容:一句「两条路都没找到」+ 带版本的装法 + 去详情页 / 收起这两个动作。
|
|
902
|
+
* 动作回调由宿主给(`onOpenDetails` 顺带把用户送到本包配置页,那里才有「检测」按钮)。
|
|
903
|
+
* @param t - 文案口。
|
|
904
|
+
* @param props - 只取那两个动作回调。
|
|
905
|
+
* @param install - 装法那一句(host 按核对版本与部署偏好组好的原文)。
|
|
906
|
+
*/
|
|
907
|
+
function buildCliActivation(t, props, install) {
|
|
908
|
+
const text = (0, react.createElement)("div", { className: STATUS_ERROR_CLASS }, t("binaryNone"));
|
|
909
|
+
const installLine = install === "" ? null : (0, react.createElement)("div", { className: "ocr-hint" }, install);
|
|
910
|
+
const details = (0, react.createElement)("button", {
|
|
911
|
+
className: "ocr-btn",
|
|
912
|
+
type: "button",
|
|
913
|
+
onClick: props.onOpenDetails,
|
|
914
|
+
"data-field": "guide-details"
|
|
915
|
+
}, t("binaryGuideDetails"));
|
|
916
|
+
const dismiss = (0, react.createElement)("button", {
|
|
917
|
+
className: "ocr-btn",
|
|
918
|
+
type: "button",
|
|
919
|
+
onClick: props.onDismiss,
|
|
920
|
+
"data-field": "guide-dismiss"
|
|
921
|
+
}, t("binaryGuideDismiss"));
|
|
922
|
+
return (0, react.createElement)("div", {
|
|
923
|
+
style: {
|
|
924
|
+
padding: "8px 0",
|
|
925
|
+
display: "flex",
|
|
926
|
+
gap: 8
|
|
927
|
+
},
|
|
928
|
+
"data-field": "cli-activation"
|
|
929
|
+
}, text, installLine, details, dismiss);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* 启用后的一次性指引:只在「两条路都没找到 ocr」那一刻说话。
|
|
933
|
+
*
|
|
934
|
+
* 这是官方给第三方 bundle 的最外侧位置(`plugins.bundle.activation`,用户从列表启用本包
|
|
935
|
+
* 后由页面渲染)。列表卡片上那排标签全部来自宿主对条目自身的判定,插件注入不进去,所以
|
|
936
|
+
* 「装没装外部 CLI」能被显示的最外一处就是这里,外加详情页那只徽标。
|
|
937
|
+
*/
|
|
938
|
+
function OcrCliActivation(props) {
|
|
939
|
+
const mine = props.packageName === BUNDLE_PKG;
|
|
940
|
+
const status = useCliStatus(mine, props.loadCliStatus);
|
|
941
|
+
if (!mine || status?.source !== "none") return null;
|
|
942
|
+
return buildCliActivation(props.t, props, cliInstallText(status));
|
|
943
|
+
}
|
|
472
944
|
/** 数据齐(source.status === "ready")那一支:provider/model 两行 + 三个动作按钮 +
|
|
473
945
|
* 当前配置与 key 两行提示。拆出来是因为这一支画的是整块表单,与上面三条「一行提示」
|
|
474
946
|
* 的降级支本就不是同一件事。 */
|
|
@@ -512,21 +984,21 @@ function buildReadyProviderSection(deps, data) {
|
|
|
512
984
|
const applyBtn = (0, react.createElement)("button", {
|
|
513
985
|
className: "ocr-btn ocr-btn-primary",
|
|
514
986
|
type: "button",
|
|
515
|
-
disabled: deps.busy,
|
|
987
|
+
disabled: deps.busy !== null,
|
|
516
988
|
onClick: deps.onApply,
|
|
517
989
|
"data-field": "apply"
|
|
518
|
-
}, deps.busy ? t("applying") : t("applyToOcr"));
|
|
990
|
+
}, deps.busy === "apply" ? t("applying") : t("applyToOcr"));
|
|
519
991
|
const testBtn = (0, react.createElement)("button", {
|
|
520
992
|
className: "ocr-btn",
|
|
521
993
|
type: "button",
|
|
522
|
-
disabled: deps.busy,
|
|
994
|
+
disabled: deps.busy !== null,
|
|
523
995
|
onClick: deps.onTest,
|
|
524
996
|
"data-field": "test"
|
|
525
997
|
}, t("testConnection"));
|
|
526
998
|
const migrateBtn = (0, react.createElement)("button", {
|
|
527
999
|
className: "ocr-btn",
|
|
528
1000
|
type: "button",
|
|
529
|
-
disabled: deps.busy,
|
|
1001
|
+
disabled: deps.busy !== null,
|
|
530
1002
|
onClick: deps.onMigrate,
|
|
531
1003
|
"data-field": "migrate"
|
|
532
1004
|
}, t("migrateKeys"));
|
|
@@ -541,7 +1013,12 @@ function buildReadyProviderSection(deps, data) {
|
|
|
541
1013
|
const currentHint = cur.provider === "" ? t("noProvider") : currentConfigText(t, cur);
|
|
542
1014
|
const currentInfo = (0, react.createElement)("div", { className: "ocr-hint" }, currentHint);
|
|
543
1015
|
const keyInfo = (0, react.createElement)("div", { className: "ocr-hint" }, deps.keyHint);
|
|
544
|
-
|
|
1016
|
+
const verify = data.endpointVerify ?? null;
|
|
1017
|
+
const verifyWarning = verify !== null && !verify.ok ? (0, react.createElement)("div", {
|
|
1018
|
+
className: STATUS_ERROR_CLASS,
|
|
1019
|
+
"data-field": "endpoint-verify"
|
|
1020
|
+
}, t("endpointVerifyFailed", { reason: verify.message })) : null;
|
|
1021
|
+
return (0, react.createElement)("div", {}, verifyWarning, providerRow, modelRow, actionRow, currentInfo, keyInfo);
|
|
545
1022
|
}
|
|
546
1023
|
/** Provider / Model 选择与应用区:三条降级各出一行提示(数据未就绪/读取失败/
|
|
547
1024
|
* 数据源不可用),只有 source.status === "ready" 才交给 buildReadyProviderSection 画表单。 */
|
|
@@ -549,13 +1026,13 @@ function buildProviderSection(deps) {
|
|
|
549
1026
|
const { t } = deps;
|
|
550
1027
|
if (deps.fetchError !== null) {
|
|
551
1028
|
const reason = t("loadProvidersFailed", { error: deps.fetchError });
|
|
552
|
-
return (0, react.createElement)("div", { className:
|
|
1029
|
+
return (0, react.createElement)("div", { className: STATUS_ERROR_CLASS }, reason);
|
|
553
1030
|
}
|
|
554
1031
|
if (deps.data === null) return (0, react.createElement)("div", { className: "ocr-status ocr-muted" }, t("loadingProviders"));
|
|
555
1032
|
if (deps.data.source.status !== "ready") {
|
|
556
1033
|
const sourceMessage = deps.data.source.message;
|
|
557
1034
|
const line = t("readProvidersFailed", { reason: sourceMessage === "" ? t("noReason") : sourceMessage });
|
|
558
|
-
return (0, react.createElement)("div", { className:
|
|
1035
|
+
return (0, react.createElement)("div", { className: STATUS_ERROR_CLASS }, line);
|
|
559
1036
|
}
|
|
560
1037
|
return buildReadyProviderSection(deps, deps.data);
|
|
561
1038
|
}
|
|
@@ -693,16 +1170,21 @@ function buildSettingsSection(deps, writable, setField) {
|
|
|
693
1170
|
const statusHintNode = deps.statusHint !== null && deps.statusHint !== "" ? (0, react.createElement)("div", { className: "ocr-hint" }, deps.statusHint) : null;
|
|
694
1171
|
return (0, react.createElement)("div", { style: { padding: "2px 0" } }, statusHintNode, effortRow, languageRow, verifyRow, maxCommentsRow, timeoutRow, configPathRow, saveRow);
|
|
695
1172
|
}
|
|
696
|
-
/**
|
|
697
|
-
|
|
1173
|
+
/**
|
|
1174
|
+
* 读设置页载荷(卡片唯一的数据入口):失败只落 fetchError 文本,不抛穿渲染。
|
|
1175
|
+
* 拒绝回执(`ok:false`)里的 error 就是「为什么读不到」的可读原因;只报一句
|
|
1176
|
+
* 「读取失败」等于把最有用的半句丢掉。
|
|
1177
|
+
*/
|
|
1178
|
+
async function refreshProviders(reader, t, sinks) {
|
|
698
1179
|
sinks.setFetchError(null);
|
|
699
1180
|
try {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1181
|
+
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1182
|
+
const json = await reader.providers();
|
|
1183
|
+
if (n(json, "ok") === false) {
|
|
1184
|
+
const detail = strOf(n(json, "error"));
|
|
1185
|
+
throw new Error(detail === "" ? t("remoteUnavailable") : detail);
|
|
704
1186
|
}
|
|
705
|
-
const payload = providersPayloadOf(
|
|
1187
|
+
const payload = providersPayloadOf(json);
|
|
706
1188
|
sinks.setData(payload);
|
|
707
1189
|
return payload;
|
|
708
1190
|
} catch (error) {
|
|
@@ -710,49 +1192,58 @@ async function refreshProviders(sinks) {
|
|
|
710
1192
|
return null;
|
|
711
1193
|
}
|
|
712
1194
|
}
|
|
713
|
-
/**
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1195
|
+
/** 把读取器接成动作函数认的那个 `post` 形状:拒绝回执原样交回,传输失败才抛。 */
|
|
1196
|
+
function readerPost(reader, t) {
|
|
1197
|
+
return async (action, body) => {
|
|
1198
|
+
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1199
|
+
switch (action) {
|
|
1200
|
+
case "select": {
|
|
1201
|
+
const provider = strOf(n(body, "provider"));
|
|
1202
|
+
const model = strOf(n(body, "model"));
|
|
1203
|
+
const reply = await reader.select(provider, model);
|
|
1204
|
+
return { json: () => Promise.resolve(reply) };
|
|
1205
|
+
}
|
|
1206
|
+
case "migrate": {
|
|
1207
|
+
const reply = await reader.migrate();
|
|
1208
|
+
return { json: () => Promise.resolve(reply) };
|
|
1209
|
+
}
|
|
1210
|
+
case "test": {
|
|
1211
|
+
const reply = await reader.test();
|
|
1212
|
+
return { json: () => Promise.resolve(reply) };
|
|
1213
|
+
}
|
|
1214
|
+
case "probe": {
|
|
1215
|
+
const reply = await reader.probe();
|
|
1216
|
+
return { json: () => Promise.resolve(reply) };
|
|
1217
|
+
}
|
|
1218
|
+
default: throw new Error(`unknown remote action: ${String(action)}`);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
730
1221
|
}
|
|
731
1222
|
/** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。 */
|
|
732
1223
|
async function runApplySelection(deps) {
|
|
733
|
-
const { t
|
|
1224
|
+
const { t, effectiveProvider, effectiveModel } = deps;
|
|
734
1225
|
if (!effectiveProvider || !effectiveModel) {
|
|
735
1226
|
deps.setActionMsg({
|
|
736
1227
|
ok: false,
|
|
737
|
-
text: t
|
|
1228
|
+
text: t("pickProviderModel")
|
|
738
1229
|
});
|
|
739
1230
|
return;
|
|
740
1231
|
}
|
|
741
|
-
deps.setBusy(
|
|
1232
|
+
deps.setBusy("apply");
|
|
742
1233
|
deps.setActionMsg(null);
|
|
743
1234
|
try {
|
|
744
|
-
const selJson = await (await deps.post(
|
|
1235
|
+
const selJson = await (await deps.post("select", {
|
|
745
1236
|
provider: effectiveProvider,
|
|
746
1237
|
model: effectiveModel
|
|
747
|
-
}))
|
|
748
|
-
if (
|
|
1238
|
+
})).json();
|
|
1239
|
+
if (n(selJson, "ok") === true) {
|
|
749
1240
|
let testText;
|
|
750
|
-
const llmTestRaw =
|
|
751
|
-
if (e(llmTestRaw)) {
|
|
752
|
-
const testOutput = strOf(
|
|
753
|
-
testText =
|
|
754
|
-
} else testText = t
|
|
755
|
-
const appliedText = t
|
|
1241
|
+
const llmTestRaw = n(selJson, "llmTest");
|
|
1242
|
+
if (e$2(llmTestRaw)) {
|
|
1243
|
+
const testOutput = strOf(n(llmTestRaw, "output")).slice(0, 400);
|
|
1244
|
+
testText = n(llmTestRaw, "ok") === true ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
|
|
1245
|
+
} else testText = t("notVerified");
|
|
1246
|
+
const appliedText = t("applied", {
|
|
756
1247
|
provider: effectiveProvider,
|
|
757
1248
|
model: effectiveModel,
|
|
758
1249
|
test: testText
|
|
@@ -763,71 +1254,105 @@ async function runApplySelection(deps) {
|
|
|
763
1254
|
});
|
|
764
1255
|
deps.refresh();
|
|
765
1256
|
} else {
|
|
766
|
-
const failure = strOf(
|
|
1257
|
+
const failure = strOf(n(selJson, "error"));
|
|
767
1258
|
deps.setActionMsg({
|
|
768
1259
|
ok: false,
|
|
769
|
-
text: failure === "" ? t
|
|
1260
|
+
text: failure === "" ? t("applyFailed") : failure
|
|
770
1261
|
});
|
|
771
1262
|
}
|
|
772
1263
|
} catch (error) {
|
|
773
|
-
const failedText = `${t
|
|
1264
|
+
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
774
1265
|
deps.setActionMsg({
|
|
775
1266
|
ok: false,
|
|
776
1267
|
text: failedText
|
|
777
1268
|
});
|
|
778
1269
|
} finally {
|
|
779
|
-
deps.setBusy(
|
|
1270
|
+
deps.setBusy(null);
|
|
780
1271
|
}
|
|
781
1272
|
}
|
|
782
1273
|
/** POST migrate:明文 api_key 一键迁到 api_key_cmd,回执把迁移/跳过两个名单拼成一行。 */
|
|
783
1274
|
async function runMigrateKeys(deps) {
|
|
784
|
-
const { t
|
|
785
|
-
deps.setBusy(
|
|
1275
|
+
const { t } = deps;
|
|
1276
|
+
deps.setBusy("migrate");
|
|
786
1277
|
deps.setActionMsg(null);
|
|
787
1278
|
try {
|
|
788
|
-
const json = await (await deps.post(
|
|
789
|
-
const migratedVal =
|
|
790
|
-
const skippedVal =
|
|
1279
|
+
const json = await (await deps.post("migrate")).json();
|
|
1280
|
+
const migratedVal = n(json, "migrated");
|
|
1281
|
+
const skippedVal = n(json, "skippedUnknown");
|
|
791
1282
|
const migrated = Array.isArray(migratedVal) ? migratedVal.filter((item) => typeof item === "string") : [];
|
|
792
1283
|
const skipped = Array.isArray(skippedVal) ? skippedVal.filter((item) => typeof item === "string") : [];
|
|
793
|
-
const ok =
|
|
794
|
-
const head = ok ? (migrated.length > 0 ? t
|
|
1284
|
+
const ok = n(json, "ok") === true;
|
|
1285
|
+
const head = ok ? (migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey")) + (skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "") : t("migrateFailed");
|
|
795
1286
|
deps.setActionMsg({
|
|
796
1287
|
ok,
|
|
797
1288
|
text: head
|
|
798
1289
|
});
|
|
799
1290
|
deps.refresh();
|
|
800
1291
|
} catch (error) {
|
|
801
|
-
const failedText = `${t
|
|
1292
|
+
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
802
1293
|
deps.setActionMsg({
|
|
803
1294
|
ok: false,
|
|
804
1295
|
text: failedText
|
|
805
1296
|
});
|
|
806
1297
|
} finally {
|
|
807
|
-
deps.setBusy(
|
|
1298
|
+
deps.setBusy(null);
|
|
808
1299
|
}
|
|
809
1300
|
}
|
|
810
1301
|
/** POST test:手动触发 ocr llm test,输出原样回显在动作条上。 */
|
|
811
1302
|
async function runTestConnection(deps) {
|
|
812
|
-
const { t
|
|
813
|
-
deps.setBusy(
|
|
1303
|
+
const { t } = deps;
|
|
1304
|
+
deps.setBusy("test");
|
|
814
1305
|
deps.setActionMsg(null);
|
|
815
1306
|
try {
|
|
816
|
-
const json = await (await deps.post(
|
|
817
|
-
const ok =
|
|
818
|
-
const testOutput = strOf(
|
|
1307
|
+
const json = await (await deps.post("test")).json();
|
|
1308
|
+
const ok = n(json, "ok") === true;
|
|
1309
|
+
const testOutput = strOf(n(json, "output"));
|
|
819
1310
|
deps.setActionMsg({
|
|
820
1311
|
ok,
|
|
821
|
-
text: (ok ? t
|
|
1312
|
+
text: (ok ? t("llmTestPassed") : t("llmTestFailed")) + testOutput
|
|
822
1313
|
});
|
|
823
1314
|
} catch (error) {
|
|
824
|
-
const failedText = `${t
|
|
1315
|
+
const failedText = `${t("requestFailed")}${errorSummary(error)}`;
|
|
825
1316
|
deps.setActionMsg({
|
|
826
1317
|
ok: false,
|
|
827
1318
|
text: failedText
|
|
828
1319
|
});
|
|
829
1320
|
} finally {
|
|
830
|
-
deps.setBusy(
|
|
1321
|
+
deps.setBusy(null);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* POST probe:跑一次 `ocr version`。
|
|
1326
|
+
*
|
|
1327
|
+
* 回执里的 `output` 原样附上,不从中抽「有新版本」那句——那是上游的措辞,抽一次就多
|
|
1328
|
+
* 一处会跟着漂的判据;那段文本本来就带在页面上供人选中复制(含上游给的 `npm i -g …`)。
|
|
1329
|
+
*/
|
|
1330
|
+
async function runProbe(deps) {
|
|
1331
|
+
const { t } = deps;
|
|
1332
|
+
deps.setBusy("probe");
|
|
1333
|
+
deps.setProbeMsg(null);
|
|
1334
|
+
try {
|
|
1335
|
+
const json = await (await deps.post("probe")).json();
|
|
1336
|
+
const ok = n(json, "ok") === true;
|
|
1337
|
+
const head = ok ? t("probeVersion", { version: strOf(n(json, "version")) }) : t("probeReason", { reason: strOf(n(json, "reason")) });
|
|
1338
|
+
const output = strOf(n(json, "output"));
|
|
1339
|
+
const lines = [
|
|
1340
|
+
head,
|
|
1341
|
+
strOf(n(json, "changeText")),
|
|
1342
|
+
output
|
|
1343
|
+
].filter((line) => line !== "");
|
|
1344
|
+
deps.setProbeMsg({
|
|
1345
|
+
ok,
|
|
1346
|
+
text: lines.join("\n")
|
|
1347
|
+
});
|
|
1348
|
+
deps.refresh();
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
deps.setProbeMsg({
|
|
1351
|
+
ok: false,
|
|
1352
|
+
text: `${t("requestFailed")}${errorSummary(error)}`
|
|
1353
|
+
});
|
|
1354
|
+
} finally {
|
|
1355
|
+
deps.setBusy(null);
|
|
831
1356
|
}
|
|
832
1357
|
}
|
|
833
1358
|
/** 只写差异字段:touched 值为 undefined → 写 null(= 清回 schema 默认)。
|
|
@@ -836,7 +1361,7 @@ async function runTestConnection(deps) {
|
|
|
836
1361
|
* 传输失败仍以 rejection 表达。 */
|
|
837
1362
|
async function saveSettingsDraft(deps) {
|
|
838
1363
|
const { t, value, touched } = deps;
|
|
839
|
-
const keys =
|
|
1364
|
+
const keys = e(touched, value);
|
|
840
1365
|
if (keys.length === 0) return;
|
|
841
1366
|
deps.setSettingsBusy(true);
|
|
842
1367
|
deps.setSaveError(null);
|
|
@@ -885,7 +1410,7 @@ function cardHints(t, ready, status, data) {
|
|
|
885
1410
|
function buildProviderPanel(deps) {
|
|
886
1411
|
const { actionMsg } = deps;
|
|
887
1412
|
const providerSection = buildProviderSection(deps);
|
|
888
|
-
const actionMsgNode = actionMsg ? (0, react.createElement)("div", { className:
|
|
1413
|
+
const actionMsgNode = actionMsg ? (0, react.createElement)("div", { className: statusClass(actionMsg.ok) }, actionMsg.text) : null;
|
|
889
1414
|
return (0, react.createElement)("div", { style: { padding: "2px 0" } }, providerSection, actionMsgNode);
|
|
890
1415
|
}
|
|
891
1416
|
function OcrCard(props) {
|
|
@@ -896,19 +1421,17 @@ function OcrCard(props) {
|
|
|
896
1421
|
const writable = snap.writable && ready;
|
|
897
1422
|
const [data, setData] = (0, react.useState)(null);
|
|
898
1423
|
const [fetchError, setFetchError] = (0, react.useState)(null);
|
|
899
|
-
const [busy, setBusy] = (0, react.useState)(
|
|
1424
|
+
const [busy, setBusy] = (0, react.useState)(null);
|
|
900
1425
|
const [actionMsg, setActionMsg] = (0, react.useState)(null);
|
|
901
|
-
const
|
|
1426
|
+
const [probeMsg, setProbeMsg] = (0, react.useState)(null);
|
|
1427
|
+
const refresh = (0, react.useCallback)(async () => refreshProviders(props.reader, t, {
|
|
902
1428
|
setData,
|
|
903
1429
|
setFetchError
|
|
904
|
-
});
|
|
1430
|
+
}), [props.reader, t]);
|
|
905
1431
|
(0, react.useEffect)(() => {
|
|
906
1432
|
refresh();
|
|
907
|
-
}, []);
|
|
908
|
-
const post =
|
|
909
|
-
csrf: data?.csrf ?? "",
|
|
910
|
-
refresh
|
|
911
|
-
});
|
|
1433
|
+
}, [refresh]);
|
|
1434
|
+
const post = readerPost(props.reader, t);
|
|
912
1435
|
const [selProvider, setSelProvider] = (0, react.useState)("");
|
|
913
1436
|
const [selModel, setSelModel] = (0, react.useState)("");
|
|
914
1437
|
const { effectiveProvider, effectiveModel, chosenProvider, modelOptions } = resolveSelection(data?.providers ?? [], data?.current, selProvider, selModel);
|
|
@@ -930,7 +1453,7 @@ function OcrCard(props) {
|
|
|
930
1453
|
const [touched, setTouched] = (0, react.useState)({});
|
|
931
1454
|
const [settingsBusy, setSettingsBusy] = (0, react.useState)(false);
|
|
932
1455
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
933
|
-
const dirty =
|
|
1456
|
+
const dirty = e(touched, value).length > 0;
|
|
934
1457
|
const setField = (field, draft) => {
|
|
935
1458
|
setTouched((prev) => ({
|
|
936
1459
|
...prev,
|
|
@@ -954,6 +1477,8 @@ function OcrCard(props) {
|
|
|
954
1477
|
chosenProvider,
|
|
955
1478
|
modelOptions,
|
|
956
1479
|
...cardHints(t, ready, snap.status, data),
|
|
1480
|
+
binary: data?.ocrBinary ?? null,
|
|
1481
|
+
probeMsg,
|
|
957
1482
|
...draftFieldsOf(touched, value),
|
|
958
1483
|
dirty,
|
|
959
1484
|
settingsBusy,
|
|
@@ -969,6 +1494,15 @@ function OcrCard(props) {
|
|
|
969
1494
|
onMigrate: () => {
|
|
970
1495
|
runMigrateKeys(actions);
|
|
971
1496
|
},
|
|
1497
|
+
onProbe: () => {
|
|
1498
|
+
runProbe({
|
|
1499
|
+
t,
|
|
1500
|
+
post,
|
|
1501
|
+
refresh,
|
|
1502
|
+
setBusy,
|
|
1503
|
+
setProbeMsg
|
|
1504
|
+
});
|
|
1505
|
+
},
|
|
972
1506
|
onField: setField,
|
|
973
1507
|
onSave: () => {
|
|
974
1508
|
saveSettingsDraft({
|
|
@@ -984,13 +1518,14 @@ function OcrCard(props) {
|
|
|
984
1518
|
onDiscard: discard
|
|
985
1519
|
};
|
|
986
1520
|
const providerPanel = buildProviderPanel(deps);
|
|
1521
|
+
const cliStatus = buildCliStatusRow(deps);
|
|
987
1522
|
const settingsSection = buildSettingsSection(deps, writable, setField);
|
|
988
1523
|
const headTitle = t("cardTitle");
|
|
989
1524
|
const headDesc = t("cardDescription");
|
|
990
1525
|
return (0, react.createElement)(OcrPluginCard, {
|
|
991
1526
|
title: headTitle,
|
|
992
1527
|
description: headDesc
|
|
993
|
-
}, providerPanel, settingsSection);
|
|
1528
|
+
}, cliStatus, providerPanel, settingsSection);
|
|
994
1529
|
}
|
|
995
1530
|
const inject = [
|
|
996
1531
|
"slots",
|
|
@@ -998,11 +1533,7 @@ const inject = [
|
|
|
998
1533
|
"locale"
|
|
999
1534
|
];
|
|
1000
1535
|
function apply(ctx) {
|
|
1001
|
-
if (
|
|
1002
|
-
globalThis.ocrReviewCardApplied = true;
|
|
1003
|
-
ctx.effect(() => () => {
|
|
1004
|
-
globalThis.ocrReviewCardApplied = void 0;
|
|
1005
|
-
}, "ocr-review-card: apply claim");
|
|
1536
|
+
if (!e$1(ctx, "ocrReviewCardApplied", "ocr-review-card: apply claim")) return;
|
|
1006
1537
|
ctx.effect(() => {
|
|
1007
1538
|
const tag = document.createElement("style");
|
|
1008
1539
|
tag.id = "ocr-review-card-css";
|
|
@@ -1016,6 +1547,7 @@ function apply(ctx) {
|
|
|
1016
1547
|
const store = cardStore(scope);
|
|
1017
1548
|
ctx.effect(() => ctx.locale.register(NS, UI_MESSAGES), "ocr-review-card: locale dictionaries");
|
|
1018
1549
|
const t = ctx.locale.bind(NS);
|
|
1550
|
+
const reader = createOcrReader(ctx);
|
|
1019
1551
|
ctx.slots.inject("plugins.bundle.config", () => {
|
|
1020
1552
|
return ctx.slots.register({
|
|
1021
1553
|
name: "plugins.bundle.config",
|
|
@@ -1023,22 +1555,60 @@ function apply(ctx) {
|
|
|
1023
1555
|
inject: () => ({
|
|
1024
1556
|
t,
|
|
1025
1557
|
hooks: { card: store },
|
|
1026
|
-
set: (field, value) => scope.set(field, value)
|
|
1558
|
+
set: (field, value) => scope.set(field, value),
|
|
1559
|
+
...reader === void 0 ? {} : { reader }
|
|
1027
1560
|
})
|
|
1028
1561
|
}, OcrCard);
|
|
1029
1562
|
});
|
|
1563
|
+
const cliShare = {
|
|
1564
|
+
t,
|
|
1565
|
+
loadCliStatus: () => loadCliStatus(reader)
|
|
1566
|
+
};
|
|
1567
|
+
ctx.slots.inject("plugins.detail.badge", () => ctx.slots.register({
|
|
1568
|
+
name: "plugins.detail.badge",
|
|
1569
|
+
id: `${NS}-cli-badge`,
|
|
1570
|
+
order: 100,
|
|
1571
|
+
label: "ocr-review CLI badge",
|
|
1572
|
+
inject: () => cliShare
|
|
1573
|
+
}, OcrCliBadge));
|
|
1574
|
+
ctx.slots.inject("plugins.detail.section", () => ctx.slots.register({
|
|
1575
|
+
name: "plugins.detail.section",
|
|
1576
|
+
id: `${NS}-cli-section`,
|
|
1577
|
+
order: 100,
|
|
1578
|
+
label: "ocr-review CLI section",
|
|
1579
|
+
inject: () => cliShare
|
|
1580
|
+
}, OcrCliSection));
|
|
1581
|
+
ctx.slots.inject("plugins.bundle.activation", () => ctx.slots.register({
|
|
1582
|
+
name: "plugins.bundle.activation",
|
|
1583
|
+
key: BUNDLE_PKG,
|
|
1584
|
+
inject: () => cliShare
|
|
1585
|
+
}, OcrCliActivation));
|
|
1030
1586
|
}
|
|
1031
1587
|
//#endregion
|
|
1032
1588
|
exports.OcrCard = OcrCard;
|
|
1589
|
+
exports.OcrCliActivation = OcrCliActivation;
|
|
1590
|
+
exports.OcrCliBadge = OcrCliBadge;
|
|
1591
|
+
exports.OcrCliSection = OcrCliSection;
|
|
1033
1592
|
exports.SaveBar = SaveBar;
|
|
1034
1593
|
exports.apply = apply;
|
|
1594
|
+
exports.binaryOf = binaryOf;
|
|
1595
|
+
exports.binaryStatusText = binaryStatusText;
|
|
1596
|
+
exports.buildCliActivation = buildCliActivation;
|
|
1597
|
+
exports.buildCliBadge = buildCliBadge;
|
|
1598
|
+
exports.buildCliSection = buildCliSection;
|
|
1599
|
+
exports.buildCliStatusRow = buildCliStatusRow;
|
|
1035
1600
|
exports.buildProviderSection = buildProviderSection;
|
|
1036
1601
|
exports.buildSettingsSection = buildSettingsSection;
|
|
1037
1602
|
exports.cardStore = cardStore;
|
|
1603
|
+
exports.cliBadgeText = cliBadgeText;
|
|
1604
|
+
exports.cliInstallText = cliInstallText;
|
|
1605
|
+
exports.cliVersionText = cliVersionText;
|
|
1038
1606
|
exports.currentConfigText = currentConfigText;
|
|
1039
|
-
exports.diffTouched = diffTouched;
|
|
1040
1607
|
exports.inject = inject;
|
|
1608
|
+
exports.isOwnSubject = isOwnSubject;
|
|
1609
|
+
exports.loadCliStatus = loadCliStatus;
|
|
1041
1610
|
exports.providerOptionLabel = providerOptionLabel;
|
|
1611
|
+
exports.runProbe = runProbe;
|
|
1042
1612
|
return module.exports;
|
|
1043
1613
|
},
|
|
1044
1614
|
});
|