dsh-chef 0.1.0 → 0.2.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/dist/client.js CHANGED
@@ -1,20 +1,470 @@
1
- /** dsh-chef 客户端存根(P10 脚手架)。
2
- *
3
- * 写死槽位原生组件,不做动态按需加载,不做外嵌页。
4
- * 面板取数只经 host.call 进 host 桥(见 bridge.ts),不 import 技能实现。
5
- * 缺席纯条件渲染,无轮询(本文件无任何定时器)。
6
- */
7
- import { TAB_COMPONENT, registerSingle, openSingle } from './slot.js';
8
- import { HOST_CALL_METHOD } from './bridge.js';
9
- export const CLIENT_COMPONENT = TAB_COMPONENT;
10
- export const CLIENT_METHOD = HOST_CALL_METHOD;
11
- export function mountSingleClient(port) {
12
- return registerSingle(port);
13
- }
14
- export function openSingleClient(port, sessionId) {
15
- openSingle(port, sessionId);
16
- }
17
- export function requestReadViaHost(host, key, params = {}) {
18
- return host.call(HOST_CALL_METHOD, { key, params });
19
- }
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-chef",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
+ key = keys[i];
17
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
+ get: ((k) => from[k]).bind(null, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+ //#endregion
29
+ let react = require("react");
30
+ react = __toESM(react, 1);
31
+ //#region src/slot.ts
32
+ const SLOT_TITLE = "大厨";
33
+ const PLUGIN = "dsh-chef";
34
+ //#endregion
35
+ //#region src/settings.ts
36
+ /** 常用项:改了就影响「库与产物落在哪」,放页面上。 */
37
+ const COMMON = [
38
+ {
39
+ key: "db.dir",
40
+ title: "数据目录",
41
+ tier: "common",
42
+ control: "text",
43
+ hint: "库文件与产物的根目录。留空=按默认落点(配置目录下的 data/),也就是改造前那个位置。"
44
+ },
45
+ {
46
+ key: "db.name",
47
+ title: "库文件名",
48
+ tier: "common",
49
+ control: "text",
50
+ hint: "数据目录下的数据库文件名。改它等于换一个菜谱库,老菜谱留在旧文件里不会跟过来。"
51
+ },
52
+ {
53
+ key: "html.dir",
54
+ title: "HTML 产物目录名",
55
+ tier: "common",
56
+ control: "text",
57
+ hint: "数据目录下放交付页面的子目录名,可带一层:默认两段 cook_html/help。改它之后新页面落新地方。"
58
+ }
59
+ ];
60
+ /** 高级项:默认收起。改错了多半只是自己撞上麻烦,不确定就别动。 */
61
+ const ADVANCED = [{
62
+ key: "files.help",
63
+ title: "产物文件名主体 · HELP",
64
+ tier: "advanced",
65
+ control: "text",
66
+ hint: "HELP 文件的文件名前缀,后面自动跟时间戳。改它之后旧的 HELP 产物不再被复用。"
67
+ }, {
68
+ key: "files.lookup",
69
+ title: "产物文件名主体 · 速查表",
70
+ tier: "advanced",
71
+ control: "text",
72
+ hint: "速查表文件的文件名前缀。改它之后新产物落新名字,旧的留在原处。"
73
+ }];
74
+ /** 设置页的行表,顺序即页面顺序。 */
75
+ const CONFIG_ITEMS = [...COMMON, ...ADVANCED];
76
+ /** 常用项行数(页面上直接画出来的那些)。 */
77
+ const COMMON_ITEM_COUNT = COMMON.length;
78
+ /** 高级组标题与副文案。 */
79
+ const ADVANCED_GROUP_TITLE = "高级";
80
+ const ADVANCED_GROUP_NOTE = "不常改。留空=按默认落点(改造前那个位置),不确定就别动。";
81
+ /** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */
82
+ function readPath(values, key) {
83
+ const parts = key.split(".");
84
+ let cur = values;
85
+ for (const part of parts) {
86
+ if (typeof cur !== "object" || cur === null || Array.isArray(cur)) return void 0;
87
+ cur = cur[part];
88
+ }
89
+ return cur;
90
+ }
91
+ /** 按 `a.b` 路径把值写进配置取值(中间层缺就建一层空组;已存在非组即覆盖成组)。 */
92
+ function writePath(values, key, value) {
93
+ const parts = key.split(".");
94
+ let cur = values;
95
+ for (let i = 0; i < parts.length - 1; i++) {
96
+ const part = parts[i];
97
+ const next = cur[part];
98
+ if (typeof next !== "object" || next === null || Array.isArray(next)) cur[part] = {};
99
+ cur = cur[part];
100
+ }
101
+ cur[parts[parts.length - 1]] = value;
102
+ }
103
+ //#endregion
104
+ //#region src/contract.ts
105
+ /** dsh-chef 端口(port):DSH 世界与私家大厨六边形之间的契约。
106
+ *
107
+ * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client 适配器(调用 RPC)
108
+ * 共用同一份定义。通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。
109
+ *
110
+ * 照 #56 卡路里/#677 记账样板同形:大厨插件原先**没有任何 RPC 通道**,本件是它第一份端口定义
111
+ * (#696:设置页要读改配置,走的就是这条通道)。
112
+ */
113
+ const RPC_CHANNEL = "/ilife-chef";
114
+ /** #696 配置端点:读设置页整面、保存一份取值、重置为默认。同走一条通道,靠端点名分发。 */
115
+ const RPC_ENDPOINT_CONFIG_GET = "config.get";
116
+ const RPC_ENDPOINT_CONFIG_SAVE = "config.save";
117
+ const RPC_ENDPOINT_CONFIG_RESET = "config.reset";
118
+ /** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
119
+ function isRpcResult(raw) {
120
+ if (typeof raw !== "object" || raw === null) return false;
121
+ const okFlag = raw.ok;
122
+ return okFlag === true || okFlag === false;
123
+ }
124
+ //#endregion
125
+ //#region src/client.ts
126
+ /** dsh-chef client 适配器:技能设置页(#696 起是真能配的页,只配置、不干活)。
127
+ *
128
+ * 三态:loading(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引,不返空、不转圈)。
129
+ * 页面只经宿主通道 `/api/ilife-chef` 的三个配置端点读改配置;**没有任何干活入口**——
130
+ * 搜菜、记做菜在技能功能页(sidebar 槽),不在本文件。
131
+ *
132
+ * 产物经 tsdown 打成 loader 工厂包(browser/CJS,见 tsdown.config.ts)。
133
+ * 本文件传递闭包:禁 node、禁 DOM 直写(document/window/process),
134
+ * 不 import 任何 host 实现(含 bridge,见 #130 纪律),只用自家 contract/settings/slot 与 dsh-ctx 的 type。
135
+ * 组件 React.createElement 手写,不引入 JSX。
136
+ */
137
+ const inject = ["slots", "connection"];
138
+ /** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */
139
+ const S = {
140
+ card: {
141
+ padding: "12px 14px",
142
+ borderRadius: 10,
143
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.35))",
144
+ background: "var(--dsw-alias-bg-base, transparent)",
145
+ color: "var(--dsw-alias-label-primary, inherit)",
146
+ fontSize: 13,
147
+ lineHeight: 1.6
148
+ },
149
+ title: {
150
+ fontSize: 14,
151
+ fontWeight: 700,
152
+ marginBottom: 8
153
+ },
154
+ muted: {
155
+ color: "var(--dsw-alias-label-secondary, #9a9a9a)",
156
+ fontSize: 12
157
+ },
158
+ info: {
159
+ color: "var(--dsw-alias-label-secondary, #9a9a9a)",
160
+ fontSize: 12,
161
+ overflowWrap: "anywhere"
162
+ },
163
+ rows: {
164
+ marginTop: 8,
165
+ borderTop: "1px solid var(--dsw-alias-border, rgba(128,128,128,.25))",
166
+ paddingTop: 8
167
+ },
168
+ row: { marginBottom: 10 },
169
+ label: { fontWeight: 600 },
170
+ hint: {
171
+ color: "var(--dsw-alias-label-secondary, #9a9a9a)",
172
+ fontSize: 12,
173
+ marginBottom: 4
174
+ },
175
+ input: {
176
+ width: "100%",
177
+ padding: "4px 8px",
178
+ borderRadius: 6,
179
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
180
+ background: "var(--dsw-alias-bg-base, #fff)",
181
+ color: "inherit",
182
+ fontSize: 13
183
+ },
184
+ advanced: { marginTop: 12 },
185
+ summary: {
186
+ cursor: "pointer",
187
+ fontWeight: 600
188
+ },
189
+ bar: {
190
+ display: "flex",
191
+ gap: 8,
192
+ marginTop: 12,
193
+ flexWrap: "wrap"
194
+ },
195
+ btn: {
196
+ padding: "4px 12px",
197
+ borderRadius: 6,
198
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
199
+ background: "transparent",
200
+ color: "inherit",
201
+ cursor: "pointer",
202
+ fontSize: 13
203
+ },
204
+ btnPrimary: {
205
+ padding: "4px 12px",
206
+ borderRadius: 6,
207
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
208
+ background: "var(--dsw-alias-brand-primary, #2f6fed)",
209
+ color: "#fff",
210
+ cursor: "pointer",
211
+ fontSize: 13
212
+ },
213
+ okText: {
214
+ marginTop: 8,
215
+ color: "var(--dsw-alias-label-success, #12805c)",
216
+ fontSize: 12
217
+ },
218
+ error: {
219
+ marginTop: 8,
220
+ color: "var(--dsw-alias-label-error, #b3261e)",
221
+ fontSize: 12,
222
+ whiteSpace: "pre-wrap"
223
+ }
224
+ };
225
+ /** 请求超时毫秒:与 host 侧 SPAWN_TIMEOUT_MS 同级,UI 永不无限转圈。 */
226
+ const READ_TIMEOUT_MS = 2e4;
227
+ /** 配置面报错 → 人话指引。
228
+ *
229
+ * 报文由技能侧给出:base-link-core 的 `ConfigError` 已带行号与文件名,技能 `cli/config.ts` 原样交出来,
230
+ * 插件 bridge 原样带上(`fetch-failed:出口非 0(1):<那一句>`)。所以这里**不重写报文**,
231
+ * 只按报错类补一句「接下来怎么办」——免得两处各写一套人话。
232
+ */
233
+ function humanizeConfigFailure(code, message) {
234
+ const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;
235
+ if (/解析|YAML|parse/i.test(text)) return `${text}\n改回「键: 值」的写法,或点「重置为默认」重来一份(重置会先留一份 .bak)。`;
236
+ if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\n本页只认表里这些项;多余的行删掉,或点「重置为默认」。`;
237
+ if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\n按本页给的形状填(文本填文本、数字填数字),或点「重置为默认」。`;
238
+ if (/缺席|missing-cli/i.test(text)) return `${text}\n技能出口没找着:先确认技能包已构建(packages/skill-chef/dist/cli/cmd_read.js)。`;
239
+ return `${text}\n改不动就点「重置为默认」(会先留一份 .bak),或照报文里的路径手工改配置文件。`;
240
+ }
241
+ /** 一次配置 RPC(同通道过三个端点;永不抛,失败落字)。 */
242
+ async function configRpc(call, method, payload) {
243
+ if (typeof call !== "function") return {
244
+ ok: false,
245
+ message: "宿主连接缺席:connection.rpc.call 不可用"
246
+ };
247
+ try {
248
+ const raw = await call("/api", RPC_CHANNEL.slice(1), {
249
+ method,
250
+ payload
251
+ }, AbortSignal.timeout(READ_TIMEOUT_MS));
252
+ if (!isRpcResult(raw)) return {
253
+ ok: false,
254
+ message: "回执信封异常(非 ok 信封)"
255
+ };
256
+ if (!raw.ok) return {
257
+ ok: false,
258
+ message: humanizeConfigFailure(raw.error?.code ?? "unknown", raw.error?.message ?? "")
259
+ };
260
+ return {
261
+ ok: true,
262
+ surface: raw.value
263
+ };
264
+ } catch (e) {
265
+ if (e instanceof Error && (e.name === "AbortError" || e.name === "TimeoutError")) return {
266
+ ok: false,
267
+ message: `配置请求超时(${Math.round(READ_TIMEOUT_MS / 1e3)}s):宿主未回`
268
+ };
269
+ return {
270
+ ok: false,
271
+ message: `配置失败:${e instanceof Error ? e.message : String(e)}`
272
+ };
273
+ }
274
+ }
275
+ /** 读设置页整面。 */
276
+ function fetchConfigSurface(call) {
277
+ return configRpc(call, RPC_ENDPOINT_CONFIG_GET, {});
278
+ }
279
+ /** 保存一份取值。 */
280
+ function saveConfigSurface(call, values) {
281
+ return configRpc(call, RPC_ENDPOINT_CONFIG_SAVE, { values });
282
+ }
283
+ /** 重置为默认(技能侧先落 .bak)。 */
284
+ function resetConfigSurface(call) {
285
+ return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});
286
+ }
287
+ /** 把配置取值铺成「行键 → 输入框文本」(缺项即空串,不返空留白)。 */
288
+ function toDraft(values) {
289
+ const draft = {};
290
+ for (const item of CONFIG_ITEMS) {
291
+ const v = readPath(values, item.key);
292
+ draft[item.key] = v === void 0 || v === null ? "" : String(v);
293
+ }
294
+ return draft;
295
+ }
296
+ /** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「未配」承担)。 */
297
+ function fromDraft(draft) {
298
+ const values = {};
299
+ for (const item of CONFIG_ITEMS) {
300
+ const raw = draft[item.key] ?? "";
301
+ const value = item.control === "number" ? Number(raw) : item.control === "switch" ? raw === "true" : raw;
302
+ writePath(values, item.key, item.control === "number" && !Number.isFinite(value) ? 0 : value);
303
+ }
304
+ return values;
305
+ }
306
+ /** 一行输入(三种控件对齐受限 YAML 子集:文本/数字/布尔)。 */
307
+ function Row(props) {
308
+ const { item } = props;
309
+ const control = item.control === "switch" ? react.createElement("input", {
310
+ type: "checkbox",
311
+ checked: props.value === "true",
312
+ disabled: props.disabled,
313
+ onChange: (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
314
+ }) : react.createElement("input", {
315
+ style: S.input,
316
+ type: item.control === "number" ? "number" : "text",
317
+ value: props.value,
318
+ disabled: props.disabled,
319
+ spellCheck: false,
320
+ onChange: (e) => props.onChange(item.key, e.target.value)
321
+ });
322
+ return react.createElement("div", { style: S.row }, react.createElement("div", { style: S.label }, item.title), react.createElement("div", { style: S.hint }, item.hint), control);
323
+ }
324
+ /** 技能设置页:承载私家大厨自己的全部可配置项(只配置,不干活)。 */
325
+ function ChefConfig(props) {
326
+ const [state, setState] = react.useState({ kind: "loading" });
327
+ const [draft, setDraft] = react.useState({});
328
+ const [busy, setBusy] = react.useState(false);
329
+ const [notice, setNotice] = react.useState(null);
330
+ const [error, setError] = react.useState(null);
331
+ const apply = react.useCallback((surface) => {
332
+ setState({
333
+ kind: "ready",
334
+ surface
335
+ });
336
+ setDraft(toDraft(surface.values));
337
+ }, []);
338
+ const load = react.useCallback(async () => {
339
+ setNotice(null);
340
+ setError(null);
341
+ const r = await fetchConfigSurface(props.getCall());
342
+ if (r.ok) apply(r.surface);
343
+ else setState({
344
+ kind: "failed",
345
+ message: r.message
346
+ });
347
+ }, [apply, props.getCall]);
348
+ react.useEffect(() => {
349
+ let alive = true;
350
+ (async () => {
351
+ const r = await fetchConfigSurface(props.getCall());
352
+ if (!alive) return;
353
+ if (r.ok) apply(r.surface);
354
+ else setState({
355
+ kind: "failed",
356
+ message: r.message
357
+ });
358
+ })();
359
+ return () => {
360
+ alive = false;
361
+ };
362
+ }, [apply, props.getCall]);
363
+ const onChange = react.useCallback((key, next) => {
364
+ setDraft((prev) => ({
365
+ ...prev,
366
+ [key]: next
367
+ }));
368
+ setNotice(null);
369
+ setError(null);
370
+ }, []);
371
+ const surface = state.kind === "ready" ? state.surface : null;
372
+ const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values)[i.key] ?? ""));
373
+ /** 写完之后重新读一份:写回执只有 {path, values},头部那两行要的是读整面。 */
374
+ const writeThenReload = react.useCallback(async (done) => {
375
+ setBusy(true);
376
+ setNotice(null);
377
+ setError(null);
378
+ const r = await fetchConfigSurface(props.getCall());
379
+ if (r.ok) {
380
+ apply(r.surface);
381
+ setNotice(done);
382
+ } else setState({
383
+ kind: "failed",
384
+ message: r.message
385
+ });
386
+ setBusy(false);
387
+ }, [apply, props.getCall]);
388
+ const onSave = react.useCallback(async () => {
389
+ setBusy(true);
390
+ setNotice(null);
391
+ setError(null);
392
+ const r = await saveConfigSurface(props.getCall(), fromDraft(draft));
393
+ setBusy(false);
394
+ if (r.ok) await writeThenReload("已保存,立即生效(不用重启宿主)");
395
+ else setError(r.message);
396
+ }, [
397
+ draft,
398
+ props.getCall,
399
+ writeThenReload
400
+ ]);
401
+ const onReset = react.useCallback(async () => {
402
+ setBusy(true);
403
+ setNotice(null);
404
+ setError(null);
405
+ const r = await resetConfigSurface(props.getCall());
406
+ setBusy(false);
407
+ if (r.ok) await writeThenReload("已重置为默认(原配置已另存一份 .bak)");
408
+ else setError(r.message);
409
+ }, [props.getCall, writeThenReload]);
410
+ const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(这份配置是刚按默认值建出来的)") : null) : null);
411
+ if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中…"));
412
+ if (state.kind === "failed") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), react.createElement("div", { style: S.error }, state.message), react.createElement("div", { style: S.bar }, react.createElement("button", {
413
+ style: S.btn,
414
+ type: "button",
415
+ onClick: () => void load()
416
+ }, "重试"), react.createElement("button", {
417
+ style: S.btn,
418
+ type: "button",
419
+ onClick: () => void onReset()
420
+ }, "重置为默认")));
421
+ const common = CONFIG_ITEMS.slice(0, COMMON_ITEM_COUNT);
422
+ const advanced = CONFIG_ITEMS.slice(COMMON_ITEM_COUNT);
423
+ const renderRow = (item) => react.createElement(Row, {
424
+ key: item.key,
425
+ item,
426
+ value: draft[item.key] ?? "",
427
+ disabled: busy,
428
+ onChange
429
+ });
430
+ return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.rows }, common.map(renderRow)), react.createElement("details", { style: S.advanced }, react.createElement("summary", { style: S.summary }, ADVANCED_GROUP_TITLE), react.createElement("div", { style: S.muted }, ADVANCED_GROUP_NOTE), advanced.map(renderRow)), react.createElement("div", { style: S.bar }, react.createElement("button", {
431
+ style: dirty ? S.btnPrimary : S.btn,
432
+ type: "button",
433
+ disabled: busy || !dirty,
434
+ onClick: () => void onSave()
435
+ }, busy ? "处理中…" : "保存"), react.createElement("button", {
436
+ style: S.btn,
437
+ type: "button",
438
+ disabled: busy,
439
+ onClick: () => void onReset()
440
+ }, "重置为默认"), react.createElement("button", {
441
+ style: S.btn,
442
+ type: "button",
443
+ disabled: busy,
444
+ onClick: () => void load()
445
+ }, "重新读取")), notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement("div", { style: S.muted }, `${PLUGIN} 只配置,不干活:搜菜与做菜在技能功能页。`));
446
+ }
447
+ function apply(ctx) {
448
+ const getCall = () => ctx.connection?.rpc?.call ?? null;
449
+ ctx.slots.inject("ilife.config-tab", () => ctx.slots.register({
450
+ name: "ilife.config-tab",
451
+ id: PLUGIN,
452
+ label: () => SLOT_TITLE,
453
+ channel: RPC_CHANNEL
454
+ }, () => react.createElement(ChefConfig, { getCall })));
455
+ }
456
+ //#endregion
457
+ exports.READ_TIMEOUT_MS = READ_TIMEOUT_MS;
458
+ exports.apply = apply;
459
+ exports.fetchConfigSurface = fetchConfigSurface;
460
+ exports.fromDraft = fromDraft;
461
+ exports.humanizeConfigFailure = humanizeConfigFailure;
462
+ exports.inject = inject;
463
+ exports.resetConfigSurface = resetConfigSurface;
464
+ exports.saveConfigSurface = saveConfigSurface;
465
+ exports.toDraft = toDraft;
466
+ return module.exports;
467
+ }
468
+ });
469
+
20
470
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAM/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAgB,CAAC;AAE9C,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAc,EAAE,SAAkB;IACjE,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAgB,EAAE,GAAW,EAAE,SAAkC,EAAE;IACpG,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AACtD,CAAC"}
1
+ {"version":3,"file":"client.js","names":["React"],"sources":["../src/slot.ts","../src/settings.ts","../src/contract.ts","../src/client.ts"],"sourcesContent":["/** dsh-chef 槽位描述(P10 脚手架,P3 #4 定案)。\n *\n * 写死槽位原生组件:id 命名空间 ilife:chef,order 90。\n * 注册与打开只走 registerTab/openTab 的路径导航;子页后缀(如 ilife:chef:*)\n * 只在主面板内导航,不占栏。缺席纯条件渲染,不轮询(本文件无定时器;\n * 有界重试归一由 base-render 拥有,单品不自建)。\n */\n\nexport const SKILL = 'chef' as const;\nexport const SLOT_ID = 'ilife:chef' as const;\nexport const SLOT_ORDER = 90 as const;\nexport const SLOT_TITLE = '大厨' as const;\nexport const PLUGIN = 'dsh-chef' as const;\nexport const MANAGER_PLUGIN = 'dsh-life-pack' as const;\n\nexport interface SlotDescriptor {\n readonly skill: string;\n readonly slotId: string;\n readonly order: number;\n readonly title: string;\n readonly kind: 'page';\n}\n\nexport function slotDescriptor(): SlotDescriptor {\n return { skill: SKILL, slotId: SLOT_ID, order: SLOT_ORDER, title: SLOT_TITLE, kind: 'page' };\n}\n\n/** 写死原生组件描述(纯数据,无按需加载,无外嵌页)。 */\nexport const TAB_COMPONENT = { kind: 'native', name: 'ChefPanel' } as const;\n\nexport interface TabsPort {\n hasTabs(): boolean;\n registerTab(entry: { readonly id: string; readonly title: string; readonly order: number; readonly single: boolean; readonly component: unknown }): () => void;\n openTab(seed: { readonly type: string; readonly path: string }, scope?: { readonly sessionId?: string }): void;\n}\n\n/** 自注册:幂等(调用方持 disposer),失败返回 false 由调用方决定重试。 */\nexport function registerSingle(port: TabsPort): (() => void) | null {\n if (!port.hasTabs()) return null;\n return port.registerTab({ id: SLOT_ID, title: SLOT_TITLE, order: SLOT_ORDER, single: true, component: TAB_COMPONENT });\n}\n\n/** 内容型打开:path seed,折叠面板自动展开。 */\nexport function openSingle(port: TabsPort, sessionId?: string): void {\n if (!port.hasTabs()) return;\n port.openTab({ type: SLOT_ID, path: SLOT_ID }, sessionId ? { sessionId } : undefined);\n}\n","/** dsh-chef 设置页元数据(#696:从只读脚手架换成真配置页)。\n *\n * 本文件是**设置页的行表**:一个可配置项一行,写清它在配置文件里的键、中文标题、\n * 分级(常用 / 高级)、控件种类与人话指引。它是页面渲染的唯一依据。\n *\n * **本文件必须零 node 依赖**:`tsconfig.client.json` 把它收进 client 侧编译,\n * 浏览器产物不许带 node 内建。默认值不住这里,也不从技能包 import——\n * 取值一律由宿主经技能 CLI 的 `chef.config.read` 端点现取(见 bridge.ts)。\n *\n * 口径:\n * · 配置表形状(键与默认值)的**唯一定义地**是技能侧 `packages/skill-chef/src/config.ts`\n * 的 `CHEF_CONFIG_DEFAULTS`(本文件是它在页面上的投影,逐键对齐由\n * `test/config-surface-696.test.mjs` 锁死);\n * · **空串=按默认落点**(老落点由技能各取用处算),所以「空」不是「没配」,\n * 而是「还走改造前那个位置」,老数据不会看起来丢了——行文案按这个语义写。\n *\n * 清单出处:「六家技能的路径类配置全量调查」大厨 5 项去掉 1 项包内固定(包内页面模板目录)\n * =**上设置页候选 4 项**;其中「产物文件名主体」在源码里是两个值(HELP/速查表),故行数为 5。\n */\nexport const SETTINGS_OWNER = 'dsh-chef' as const;\nexport const SETTINGS_SLOT = 'ilife:chef' as const;\n\n/** 配置文件主体名:落点 `~/.ilife/chef.yaml`(`ILIFE_CONFIG_DIR` 可整体接管)。 */\nexport const CONFIG_STEM = 'chef' as const;\n\n/** 分级:常用项直接画在页面上,其余进默认收起的「高级」组(#675 冻结口径)。 */\nexport type ConfigTier = 'common' | 'advanced';\n\n/** 控件种类:只有这三种(与受限 YAML 子集的字符串/数字/布尔一一对应,没有数组)。 */\nexport type ConfigControl = 'text' | 'number' | 'switch';\n\nexport interface ConfigItem {\n /** 配置文件里的键路径,一层嵌套用 `.` 连接,例 `files.help`。 */\n readonly key: string;\n /** 页面上那一行的中文标题。 */\n readonly title: string;\n readonly tier: ConfigTier;\n readonly control: ConfigControl;\n /** 一行人话:这一项管什么、留空会怎样。 */\n readonly hint: string;\n}\n\n/** 常用项:改了就影响「库与产物落在哪」,放页面上。 */\nconst COMMON: readonly ConfigItem[] = [\n {\n key: 'db.dir',\n title: '数据目录',\n tier: 'common',\n control: 'text',\n hint: '库文件与产物的根目录。留空=按默认落点(配置目录下的 data/),也就是改造前那个位置。',\n },\n {\n key: 'db.name',\n title: '库文件名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下的数据库文件名。改它等于换一个菜谱库,老菜谱留在旧文件里不会跟过来。',\n },\n {\n key: 'html.dir',\n title: 'HTML 产物目录名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下放交付页面的子目录名,可带一层:默认两段 cook_html/help。改它之后新页面落新地方。',\n },\n];\n\n/** 高级项:默认收起。改错了多半只是自己撞上麻烦,不确定就别动。 */\nconst ADVANCED: readonly ConfigItem[] = [\n {\n key: 'files.help',\n title: '产物文件名主体 · HELP',\n tier: 'advanced',\n control: 'text',\n hint: 'HELP 文件的文件名前缀,后面自动跟时间戳。改它之后旧的 HELP 产物不再被复用。',\n },\n {\n key: 'files.lookup',\n title: '产物文件名主体 · 速查表',\n tier: 'advanced',\n control: 'text',\n hint: '速查表文件的文件名前缀。改它之后新产物落新名字,旧的留在原处。',\n },\n];\n\n/** 设置页的行表,顺序即页面顺序。 */\nexport const CONFIG_ITEMS: readonly ConfigItem[] = [...COMMON, ...ADVANCED];\n\n/** 常用项行数(页面上直接画出来的那些)。 */\nexport const COMMON_ITEM_COUNT = COMMON.length;\n\n/** 高级组标题与副文案。 */\nexport const ADVANCED_GROUP_TITLE = '高级' as const;\nexport const ADVANCED_GROUP_NOTE = '不常改。留空=按默认落点(改造前那个位置),不确定就别动。' as const;\n\n/** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */\nexport function readPath(values: Record<string, unknown>, key: string): unknown {\n const parts = key.split('.');\n let cur: unknown = values;\n for (const part of parts) {\n if (typeof cur !== 'object' || cur === null || Array.isArray(cur)) return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\n/** 按 `a.b` 路径把值写进配置取值(中间层缺就建一层空组;已存在非组即覆盖成组)。 */\nexport function writePath(values: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split('.');\n let cur: Record<string, unknown> = values;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n const next = cur[part];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) cur[part] = {};\n cur = cur[part] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]!] = value;\n}\n","/** dsh-chef 端口(port):DSH 世界与私家大厨六边形之间的契约。\n *\n * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client 适配器(调用 RPC)\n * 共用同一份定义。通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。\n *\n * 照 #56 卡路里/#677 记账样板同形:大厨插件原先**没有任何 RPC 通道**,本件是它第一份端口定义\n * (#696:设置页要读改配置,走的就是这条通道)。\n */\nexport const RPC_CHANNEL = '/ilife-chef' as const;\n\n/** #696 配置端点:读设置页整面、保存一份取值、重置为默认。同走一条通道,靠端点名分发。 */\nexport const RPC_ENDPOINT_CONFIG_GET = 'config.get' as const;\nexport const RPC_ENDPOINT_CONFIG_SAVE = 'config.save' as const;\nexport const RPC_ENDPOINT_CONFIG_RESET = 'config.reset' as const;\n/** #706 配置体检端点:只读一次,回一份报告(判据由技能侧出,本包只透传,不重写一个字)。\n *\n * 端点名与技能侧那条只读命令同名(`chef.config.check`);报告形状的唯一真相在技能侧\n * `packages/skill-chef/src/health.ts`,面板侧镜像在 `packages/plugin-manager/src/health-contract.ts`。 */\nexport const RPC_ENDPOINT_CONFIG_CHECK = 'config.check' as const;\n\n/** 保存载荷:一份键值(键路径 → 值)。缺项由技能侧按默认值补齐,故只收用户真改的那些。 */\nexport interface SavePayload {\n readonly values: Record<string, unknown>;\n}\n\n/** 保存载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */\nexport function parseSavePayload(raw: unknown): SavePayload | null {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const { values } = raw as { values?: unknown };\n if (typeof values !== 'object' || values === null || Array.isArray(values)) return null;\n return { values: values as Record<string, unknown> };\n}\n\n/** 配置面回执:形状与技能侧 `chef.config.read` 的 data 逐字段同形(唯一定义地在那边的 cli/config.ts)。\n *\n * 这里不带默认值表——页面不猜默认值,「留空=按默认落点」由行文案说清,\n * 「重置为默认」由技能侧 `chef.config.reset` 执行。\n */\nexport interface ConfigSurfaceReply {\n /** 配置文件绝对路径。 */\n readonly path: string;\n /** 数据目录绝对路径。 */\n readonly dataDir: string;\n /** 本次是不是「文件不存在、按默认值落了一份」。 */\n readonly created: boolean;\n /** 当前取值(文件里的缺项按默认值补)。 */\n readonly values: Record<string, unknown>;\n}\n\nexport interface RpcError {\n readonly code: string;\n readonly message: string;\n readonly details: Record<string, unknown>;\n}\n\nexport type RpcResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: RpcError };\n\nexport function ok(value: unknown): RpcResult {\n return { ok: true, value };\n}\n\nexport function fail(code: string, message: string): RpcResult {\n return { ok: false, error: { code, message, details: {} } };\n}\n\n/** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */\nexport function isRpcResult(raw: unknown): raw is RpcResult {\n if (typeof raw !== 'object' || raw === null) return false;\n const okFlag = (raw as { ok?: unknown }).ok;\n return okFlag === true || okFlag === false;\n}\n","/** dsh-chef client 适配器:技能设置页(#696 起是真能配的页,只配置、不干活)。\n *\n * 三态:loading(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引,不返空、不转圈)。\n * 页面只经宿主通道 `/api/ilife-chef` 的三个配置端点读改配置;**没有任何干活入口**——\n * 搜菜、记做菜在技能功能页(sidebar 槽),不在本文件。\n *\n * 产物经 tsdown 打成 loader 工厂包(browser/CJS,见 tsdown.config.ts)。\n * 本文件传递闭包:禁 node、禁 DOM 直写(document/window/process),\n * 不 import 任何 host 实现(含 bridge,见 #130 纪律),只用自家 contract/settings/slot 与 dsh-ctx 的 type。\n * 组件 React.createElement 手写,不引入 JSX。\n */\n\nimport * as React from 'react';\nimport { PLUGIN, SLOT_TITLE } from './slot.js';\nimport {\n CONFIG_ITEMS, COMMON_ITEM_COUNT, ADVANCED_GROUP_TITLE, ADVANCED_GROUP_NOTE, readPath, writePath,\n} from './settings.js';\nimport type { ConfigItem } from './settings.js';\nimport {\n RPC_CHANNEL, RPC_ENDPOINT_CONFIG_GET, RPC_ENDPOINT_CONFIG_SAVE, RPC_ENDPOINT_CONFIG_RESET, isRpcResult,\n} from './contract.js';\nimport type { ConfigSurfaceReply } from './contract.js';\nimport type { ClientCtx, RpcCallFace } from './dsh-ctx.js';\n\nexport const inject: readonly string[] = ['slots', 'connection'];\n\n/** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */\nconst S = {\n card: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontSize: 13,\n lineHeight: 1.6,\n } as React.CSSProperties,\n title: { fontSize: 14, fontWeight: 700, marginBottom: 8 } as React.CSSProperties,\n muted: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12 } as React.CSSProperties,\n info: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, overflowWrap: 'anywhere' } as React.CSSProperties,\n rows: { marginTop: 8, borderTop: '1px solid var(--dsw-alias-border, rgba(128,128,128,.25))', paddingTop: 8 } as React.CSSProperties,\n row: { marginBottom: 10 } as React.CSSProperties,\n label: { fontWeight: 600 } as React.CSSProperties,\n hint: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, marginBottom: 4 } as React.CSSProperties,\n input: {\n width: '100%',\n padding: '4px 8px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, #fff)',\n color: 'inherit',\n fontSize: 13,\n } as React.CSSProperties,\n advanced: { marginTop: 12 } as React.CSSProperties,\n summary: { cursor: 'pointer', fontWeight: 600 } as React.CSSProperties,\n bar: { display: 'flex', gap: 8, marginTop: 12, flexWrap: 'wrap' } as React.CSSProperties,\n btn: {\n padding: '4px 12px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'transparent',\n color: 'inherit',\n cursor: 'pointer',\n fontSize: 13,\n } as React.CSSProperties,\n btnPrimary: {\n padding: '4px 12px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-brand-primary, #2f6fed)',\n color: '#fff',\n cursor: 'pointer',\n fontSize: 13,\n } as React.CSSProperties,\n okText: { marginTop: 8, color: 'var(--dsw-alias-label-success, #12805c)', fontSize: 12 } as React.CSSProperties,\n error: { marginTop: 8, color: 'var(--dsw-alias-label-error, #b3261e)', fontSize: 12, whiteSpace: 'pre-wrap' } as React.CSSProperties,\n};\n\n/** 请求超时毫秒:与 host 侧 SPAWN_TIMEOUT_MS 同级,UI 永不无限转圈。 */\nexport const READ_TIMEOUT_MS = 20_000 as const;\n\ntype ConfigState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'ready'; readonly surface: ConfigSurfaceReply }\n | { readonly kind: 'failed'; readonly message: string };\n\ntype ConfigOutcome =\n | { readonly ok: true; readonly surface: ConfigSurfaceReply }\n | { readonly ok: false; readonly message: string };\n\n/** 配置面报错 → 人话指引。\n *\n * 报文由技能侧给出:base-link-core 的 `ConfigError` 已带行号与文件名,技能 `cli/config.ts` 原样交出来,\n * 插件 bridge 原样带上(`fetch-failed:出口非 0(1):<那一句>`)。所以这里**不重写报文**,\n * 只按报错类补一句「接下来怎么办」——免得两处各写一套人话。\n */\nexport function humanizeConfigFailure(code: string, message: string): string {\n const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;\n if (/解析|YAML|parse/i.test(text)) return `${text}\\n改回「键: 值」的写法,或点「重置为默认」重来一份(重置会先留一份 .bak)。`;\n if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\\n本页只认表里这些项;多余的行删掉,或点「重置为默认」。`;\n if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\\n按本页给的形状填(文本填文本、数字填数字),或点「重置为默认」。`;\n if (/缺席|missing-cli/i.test(text)) return `${text}\\n技能出口没找着:先确认技能包已构建(packages/skill-chef/dist/cli/cmd_read.js)。`;\n return `${text}\\n改不动就点「重置为默认」(会先留一份 .bak),或照报文里的路径手工改配置文件。`;\n}\n\n/** 一次配置 RPC(同通道过三个端点;永不抛,失败落字)。 */\nasync function configRpc(call: unknown, method: string, payload: Record<string, unknown>): Promise<ConfigOutcome> {\n if (typeof call !== 'function') return { ok: false, message: '宿主连接缺席:connection.rpc.call 不可用' };\n try {\n const raw: unknown = await (call as RpcCallFace)('/api', RPC_CHANNEL.slice(1), { method, payload }, AbortSignal.timeout(READ_TIMEOUT_MS));\n if (!isRpcResult(raw)) return { ok: false, message: '回执信封异常(非 ok 信封)' };\n if (!raw.ok) return { ok: false, message: humanizeConfigFailure(raw.error?.code ?? 'unknown', raw.error?.message ?? '') };\n return { ok: true, surface: raw.value as ConfigSurfaceReply };\n } catch (e) {\n if (e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError')) {\n return { ok: false, message: `配置请求超时(${Math.round(READ_TIMEOUT_MS / 1000)}s):宿主未回` };\n }\n return { ok: false, message: `配置失败:${e instanceof Error ? e.message : String(e)}` };\n }\n}\n\n/** 读设置页整面。 */\nexport function fetchConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_GET, {});\n}\n\n/** 保存一份取值。 */\nexport function saveConfigSurface(call: unknown, values: Record<string, unknown>): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_SAVE, { values });\n}\n\n/** 重置为默认(技能侧先落 .bak)。 */\nexport function resetConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});\n}\n\n/** 把配置取值铺成「行键 → 输入框文本」(缺项即空串,不返空留白)。 */\nexport function toDraft(values: Record<string, unknown>): Record<string, string> {\n const draft: Record<string, string> = {};\n for (const item of CONFIG_ITEMS) {\n const v = readPath(values, item.key);\n draft[item.key] = v === undefined || v === null ? '' : String(v);\n }\n return draft;\n}\n\n/** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「未配」承担)。 */\nexport function fromDraft(draft: Record<string, string>): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const item of CONFIG_ITEMS) {\n const raw = draft[item.key] ?? '';\n const value: unknown = item.control === 'number' ? Number(raw) : item.control === 'switch' ? raw === 'true' : raw;\n writePath(values, item.key, item.control === 'number' && !Number.isFinite(value as number) ? 0 : value);\n }\n return values;\n}\n\n/** 一行输入(三种控件对齐受限 YAML 子集:文本/数字/布尔)。 */\nfunction Row(props: {\n readonly item: ConfigItem;\n readonly value: string;\n readonly disabled: boolean;\n readonly onChange: (key: string, next: string) => void;\n}): React.ReactElement {\n const { item } = props;\n const control =\n item.control === 'switch'\n ? React.createElement('input', {\n type: 'checkbox',\n checked: props.value === 'true',\n disabled: props.disabled,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.checked ? 'true' : 'false'),\n })\n : React.createElement('input', {\n style: S.input,\n type: item.control === 'number' ? 'number' : 'text',\n value: props.value,\n disabled: props.disabled,\n spellCheck: false,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.value),\n });\n return React.createElement(\n 'div',\n { style: S.row },\n React.createElement('div', { style: S.label }, item.title),\n React.createElement('div', { style: S.hint }, item.hint),\n control,\n );\n}\n\n/** 技能设置页:承载私家大厨自己的全部可配置项(只配置,不干活)。 */\nfunction ChefConfig(props: { getCall: GetCall }): React.ReactElement {\n const [state, setState] = React.useState<ConfigState>({ kind: 'loading' });\n const [draft, setDraft] = React.useState<Record<string, string>>({});\n const [busy, setBusy] = React.useState(false);\n const [notice, setNotice] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n\n const apply = React.useCallback((surface: ConfigSurfaceReply) => {\n setState({ kind: 'ready', surface });\n setDraft(toDraft(surface.values));\n }, []);\n\n const load = React.useCallback(async () => {\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n }, [apply, props.getCall]);\n\n React.useEffect(() => {\n let alive = true;\n void (async () => {\n const r = await fetchConfigSurface(props.getCall());\n if (!alive) return;\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n })();\n return () => {\n alive = false;\n };\n }, [apply, props.getCall]);\n\n const onChange = React.useCallback((key: string, next: string) => {\n setDraft((prev) => ({ ...prev, [key]: next }));\n setNotice(null);\n setError(null);\n }, []);\n\n const surface = state.kind === 'ready' ? state.surface : null;\n const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? '') !== (toDraft(surface.values)[i.key] ?? ''));\n\n /** 写完之后重新读一份:写回执只有 {path, values},头部那两行要的是读整面。 */\n const writeThenReload = React.useCallback(async (done: string) => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) {\n apply(r.surface);\n setNotice(done);\n } else {\n setState({ kind: 'failed', message: r.message });\n }\n setBusy(false);\n }, [apply, props.getCall]);\n\n const onSave = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await saveConfigSurface(props.getCall(), fromDraft(draft));\n setBusy(false);\n if (r.ok) await writeThenReload('已保存,立即生效(不用重启宿主)');\n else setError(r.message);\n }, [draft, props.getCall, writeThenReload]);\n\n const onReset = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await resetConfigSurface(props.getCall());\n setBusy(false);\n if (r.ok) await writeThenReload('已重置为默认(原配置已另存一份 .bak)');\n else setError(r.message);\n }, [props.getCall, writeThenReload]);\n\n const head = React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n surface !== null\n ? React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.info }, `配置文件 ${surface.path}`),\n React.createElement('div', { style: S.info }, `数据目录 ${surface.dataDir}`),\n surface.created ? React.createElement('div', { style: S.muted }, '(这份配置是刚按默认值建出来的)') : null,\n )\n : null,\n );\n\n if (state.kind === 'loading') {\n return React.createElement('div', { style: S.card }, head, React.createElement('div', { style: S.muted }, '配置读取中…'));\n }\n\n if (state.kind === 'failed') {\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n React.createElement('div', { style: S.error }, state.message),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void load() }, '重试'),\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void onReset() }, '重置为默认'),\n ),\n );\n }\n\n const common = CONFIG_ITEMS.slice(0, COMMON_ITEM_COUNT);\n const advanced = CONFIG_ITEMS.slice(COMMON_ITEM_COUNT);\n const renderRow = (item: ConfigItem) =>\n React.createElement(Row, { key: item.key, item, value: draft[item.key] ?? '', disabled: busy, onChange });\n\n return React.createElement(\n 'div',\n { style: S.card },\n head,\n React.createElement('div', { style: S.rows }, common.map(renderRow)),\n React.createElement(\n 'details',\n { style: S.advanced },\n React.createElement('summary', { style: S.summary }, ADVANCED_GROUP_TITLE),\n React.createElement('div', { style: S.muted }, ADVANCED_GROUP_NOTE),\n advanced.map(renderRow),\n ),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement(\n 'button',\n { style: dirty ? S.btnPrimary : S.btn, type: 'button', disabled: busy || !dirty, onClick: () => void onSave() },\n busy ? '处理中…' : '保存',\n ),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void onReset() }, '重置为默认'),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void load() }, '重新读取'),\n ),\n notice !== null ? React.createElement('div', { style: S.okText }, notice) : null,\n error !== null ? React.createElement('div', { style: S.error }, error) : null,\n React.createElement('div', { style: S.muted }, `${PLUGIN} 只配置,不干活:搜菜与做菜在技能功能页。`),\n );\n}\n\n/** 调用口取用器(每次取数时现取:connection 后到也不永久缺席)。 */\ntype GetCall = () => unknown;\n\nexport function apply(ctx: ClientCtx): void {\n const getCall: GetCall = () => ctx.connection?.rpc?.call ?? null;\n // 技能设置页 → 爱生活页签槽(总管声明;总管缺席时 inject 等待,不断链)。\n ctx.slots.inject('ilife.config-tab', () =>\n ctx.slots.register(\n {\n name: 'ilife.config-tab',\n id: PLUGIN,\n label: () => SLOT_TITLE,\n // #706:本包自己那条 RPC 通道交给总管(各家都写这一格),总管据此调配置体检——\n // 它因此不必在源码里写死任何一家的通道名(零单品依赖照旧成立)。\n channel: RPC_CHANNEL,\n },\n () => React.createElement(ChefConfig, { getCall }),\n ),\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /** dsh-chef 端口(port):DSH 世界与私家大厨六边形之间的契约。
2
+ *
3
+ * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client 适配器(调用 RPC)
4
+ * 共用同一份定义。通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。
5
+ *
6
+ * 照 #56 卡路里/#677 记账样板同形:大厨插件原先**没有任何 RPC 通道**,本件是它第一份端口定义
7
+ * (#696:设置页要读改配置,走的就是这条通道)。
8
+ */
9
+ export declare const RPC_CHANNEL: "/ilife-chef";
10
+ /** #696 配置端点:读设置页整面、保存一份取值、重置为默认。同走一条通道,靠端点名分发。 */
11
+ export declare const RPC_ENDPOINT_CONFIG_GET: "config.get";
12
+ export declare const RPC_ENDPOINT_CONFIG_SAVE: "config.save";
13
+ export declare const RPC_ENDPOINT_CONFIG_RESET: "config.reset";
14
+ /** #706 配置体检端点:只读一次,回一份报告(判据由技能侧出,本包只透传,不重写一个字)。
15
+ *
16
+ * 端点名与技能侧那条只读命令同名(`chef.config.check`);报告形状的唯一真相在技能侧
17
+ * `packages/skill-chef/src/health.ts`,面板侧镜像在 `packages/plugin-manager/src/health-contract.ts`。 */
18
+ export declare const RPC_ENDPOINT_CONFIG_CHECK: "config.check";
19
+ /** 保存载荷:一份键值(键路径 → 值)。缺项由技能侧按默认值补齐,故只收用户真改的那些。 */
20
+ export interface SavePayload {
21
+ readonly values: Record<string, unknown>;
22
+ }
23
+ /** 保存载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */
24
+ export declare function parseSavePayload(raw: unknown): SavePayload | null;
25
+ /** 配置面回执:形状与技能侧 `chef.config.read` 的 data 逐字段同形(唯一定义地在那边的 cli/config.ts)。
26
+ *
27
+ * 这里不带默认值表——页面不猜默认值,「留空=按默认落点」由行文案说清,
28
+ * 「重置为默认」由技能侧 `chef.config.reset` 执行。
29
+ */
30
+ export interface ConfigSurfaceReply {
31
+ /** 配置文件绝对路径。 */
32
+ readonly path: string;
33
+ /** 数据目录绝对路径。 */
34
+ readonly dataDir: string;
35
+ /** 本次是不是「文件不存在、按默认值落了一份」。 */
36
+ readonly created: boolean;
37
+ /** 当前取值(文件里的缺项按默认值补)。 */
38
+ readonly values: Record<string, unknown>;
39
+ }
40
+ export interface RpcError {
41
+ readonly code: string;
42
+ readonly message: string;
43
+ readonly details: Record<string, unknown>;
44
+ }
45
+ export type RpcResult = {
46
+ readonly ok: true;
47
+ readonly value: unknown;
48
+ } | {
49
+ readonly ok: false;
50
+ readonly error: RpcError;
51
+ };
52
+ export declare function ok(value: unknown): RpcResult;
53
+ export declare function fail(code: string, message: string): RpcResult;
54
+ /** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
55
+ export declare function isRpcResult(raw: unknown): raw is RpcResult;
56
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAG,aAAsB,CAAC;AAElD,mDAAmD;AACnD,eAAO,MAAM,uBAAuB,EAAG,YAAqB,CAAC;AAC7D,eAAO,MAAM,wBAAwB,EAAG,aAAsB,CAAC;AAC/D,eAAO,MAAM,yBAAyB,EAAG,cAAuB,CAAC;AACjE;;;kGAGkG;AAClG,eAAO,MAAM,yBAAyB,EAAG,cAAuB,CAAC;AAEjE,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAKjE;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gBAAgB;IAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAC;AAErD,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAE5C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE7D;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,SAAS,CAI1D"}