@anweat/dsh-browser 0.1.7 → 0.1.9
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 +356 -245
- package/cordis.patch.yml +26 -16
- package/lib/approval-policy.js +34 -0
- package/lib/approval-policy.js.map +1 -1
- package/lib/auth-profiles.js +10 -1
- package/lib/auth-profiles.js.map +1 -1
- package/lib/automation-assets-rpc.d.ts +5 -0
- package/lib/automation-assets-rpc.js +63 -0
- package/lib/automation-assets-rpc.js.map +1 -0
- package/lib/automation-assets.d.ts +139 -0
- package/lib/automation-assets.js +372 -0
- package/lib/automation-assets.js.map +1 -0
- package/lib/automation-development.d.ts +13 -0
- package/lib/automation-development.js +37 -0
- package/lib/automation-development.js.map +1 -0
- package/lib/automation-execution.d.ts +14 -0
- package/lib/automation-execution.js +55 -0
- package/lib/automation-execution.js.map +1 -0
- package/lib/browser-service.d.ts +73 -25
- package/lib/browser-service.js +286 -74
- package/lib/browser-service.js.map +1 -1
- package/lib/client/SettingsCard.d.ts +2 -0
- package/lib/client/SettingsCard.js +69 -0
- package/lib/client/SettingsCard.js.map +1 -0
- package/lib/client/automation-assets-client.d.ts +38 -0
- package/lib/client/automation-assets-client.js +83 -0
- package/lib/client/automation-assets-client.js.map +1 -0
- package/lib/client/context-types.d.ts +8 -0
- package/lib/client/context-types.js +2 -0
- package/lib/client/context-types.js.map +1 -0
- package/lib/client/form.d.ts +62 -0
- package/lib/client/form.js +224 -0
- package/lib/client/form.js.map +1 -0
- package/lib/client/index.d.ts +27 -0
- package/lib/client/index.js +28 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client/locales.d.ts +86 -0
- package/lib/client/locales.js +65 -0
- package/lib/client/locales.js.map +1 -0
- package/lib/client/settings-namespace.d.ts +2 -0
- package/lib/client/settings-namespace.js +3 -0
- package/lib/client/settings-namespace.js.map +1 -0
- package/lib/client/styles.d.ts +48 -0
- package/lib/client/styles.js +28 -0
- package/lib/client/styles.js.map +1 -0
- package/lib/client.js +1282 -0
- package/lib/client.js.map +1 -0
- package/lib/config.d.ts +14 -0
- package/lib/config.js +43 -0
- package/lib/config.js.map +1 -1
- package/lib/deps.d.ts +7 -1
- package/lib/deps.js +22 -6
- package/lib/deps.js.map +1 -1
- package/lib/freedom.d.ts +4 -1
- package/lib/freedom.js +14 -0
- package/lib/freedom.js.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.js +20 -3
- package/lib/index.js.map +1 -1
- package/lib/opencli-catalog.d.ts +21 -0
- package/lib/opencli-catalog.js +49 -0
- package/lib/opencli-catalog.js.map +1 -0
- package/lib/scripts.d.ts +1 -1
- package/lib/scripts.js +30 -29
- package/lib/scripts.js.map +1 -1
- package/lib/tools.d.ts +2 -1
- package/lib/tools.js +222 -11
- package/lib/tools.js.map +1 -1
- package/lib/usage-policy.d.ts +49 -0
- package/lib/usage-policy.js +171 -0
- package/lib/usage-policy.js.map +1 -0
- package/package.json +133 -78
- package/scripts/check-client-bundle.mjs +15 -0
- package/scripts/clean-lib.mjs +3 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1282 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@anweat/dsh-browser",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/client/form.ts
|
|
10
|
+
const textField = (field) => ({
|
|
11
|
+
field,
|
|
12
|
+
format: (value) => typeof value === "string" ? value : "",
|
|
13
|
+
parse(text) {
|
|
14
|
+
return text.trim() === "" ? { kind: "clear" } : {
|
|
15
|
+
kind: "set",
|
|
16
|
+
value: text.trim()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const booleanField = (field) => ({
|
|
21
|
+
field,
|
|
22
|
+
format: (value) => value === true ? "true" : "false",
|
|
23
|
+
parse: (text) => text === "true" || text === "false" ? {
|
|
24
|
+
kind: "set",
|
|
25
|
+
value: text === "true"
|
|
26
|
+
} : void 0
|
|
27
|
+
});
|
|
28
|
+
const enumField = (field, values) => ({
|
|
29
|
+
field,
|
|
30
|
+
format: (value) => typeof value === "string" ? value : values[0] ?? "",
|
|
31
|
+
parse: (text) => values.includes(text) ? {
|
|
32
|
+
kind: "set",
|
|
33
|
+
value: text
|
|
34
|
+
} : void 0
|
|
35
|
+
});
|
|
36
|
+
const jsonField = (field, validate) => ({
|
|
37
|
+
field,
|
|
38
|
+
format: (value) => value && typeof value === "object" && !Array.isArray(value) ? JSON.stringify(value, null, 2) : "",
|
|
39
|
+
parse(text) {
|
|
40
|
+
if (text.trim() === "") return { kind: "clear" };
|
|
41
|
+
try {
|
|
42
|
+
const value = JSON.parse(text);
|
|
43
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
44
|
+
const record = value;
|
|
45
|
+
return validate && !validate(record) ? void 0 : {
|
|
46
|
+
kind: "set",
|
|
47
|
+
value: record
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const POLICY_BOUNDS = {
|
|
55
|
+
minDelayMs: [0, 6e4],
|
|
56
|
+
maxConcurrency: [1, 8],
|
|
57
|
+
burst: [1, 20],
|
|
58
|
+
maxPagesPerRun: [1, 100],
|
|
59
|
+
maxDepth: [0, 5],
|
|
60
|
+
retryLimit: [0, 5],
|
|
61
|
+
backoffBaseMs: [1, 6e4],
|
|
62
|
+
cooldownMs: [100, 3e5]
|
|
63
|
+
};
|
|
64
|
+
const ASSET_POLICY_KEYS = /* @__PURE__ */ new Set([
|
|
65
|
+
"enabled",
|
|
66
|
+
"directory",
|
|
67
|
+
"persistenceMode",
|
|
68
|
+
"activationMode",
|
|
69
|
+
"minSuccessfulRuns",
|
|
70
|
+
"minDistinctSessions",
|
|
71
|
+
"successWindowDays",
|
|
72
|
+
"minSuccessRate",
|
|
73
|
+
"maxCandidates",
|
|
74
|
+
"candidateTtlDays",
|
|
75
|
+
"maxSuggestionsPerDay",
|
|
76
|
+
"maxDrafts",
|
|
77
|
+
"maxActiveAssets",
|
|
78
|
+
"retrievalTopK",
|
|
79
|
+
"catalogTokenBudget",
|
|
80
|
+
"modelDevelopmentEnabled",
|
|
81
|
+
"maxModelDraftWritesPerSession"
|
|
82
|
+
]);
|
|
83
|
+
function validAssetPolicy(value) {
|
|
84
|
+
if (Object.keys(value).some((key) => !ASSET_POLICY_KEYS.has(key))) return false;
|
|
85
|
+
if (value.enabled !== void 0 && typeof value.enabled !== "boolean") return false;
|
|
86
|
+
if (value.directory !== void 0 && typeof value.directory !== "string") return false;
|
|
87
|
+
if (value.modelDevelopmentEnabled !== void 0 && typeof value.modelDevelopmentEnabled !== "boolean") return false;
|
|
88
|
+
if (value.persistenceMode !== void 0 && ![
|
|
89
|
+
"off",
|
|
90
|
+
"manual",
|
|
91
|
+
"suggest",
|
|
92
|
+
"auto-draft"
|
|
93
|
+
].includes(String(value.persistenceMode))) return false;
|
|
94
|
+
if (value.activationMode !== void 0 && !["manual", "auto-tested"].includes(String(value.activationMode))) return false;
|
|
95
|
+
return Object.entries(value).every(([key, entry]) => {
|
|
96
|
+
if (![
|
|
97
|
+
"minSuccessfulRuns",
|
|
98
|
+
"minDistinctSessions",
|
|
99
|
+
"successWindowDays",
|
|
100
|
+
"minSuccessRate",
|
|
101
|
+
"maxCandidates",
|
|
102
|
+
"candidateTtlDays",
|
|
103
|
+
"maxSuggestionsPerDay",
|
|
104
|
+
"maxDrafts",
|
|
105
|
+
"maxActiveAssets",
|
|
106
|
+
"retrievalTopK",
|
|
107
|
+
"catalogTokenBudget",
|
|
108
|
+
"maxModelDraftWritesPerSession"
|
|
109
|
+
].includes(key)) return true;
|
|
110
|
+
return typeof entry === "number" && Number.isFinite(entry) && entry >= 0;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function validUsagePolicy(value) {
|
|
114
|
+
if (Object.keys(value).some((key) => !(key in POLICY_BOUNDS))) return false;
|
|
115
|
+
return Object.entries(POLICY_BOUNDS).every(([key, [min, max]]) => {
|
|
116
|
+
const entry = value[key];
|
|
117
|
+
return entry === void 0 || typeof entry === "number" && Number.isInteger(entry) && entry >= min && entry <= max;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const FIELD_SPECS = [
|
|
121
|
+
booleanField("enabled"),
|
|
122
|
+
enumField("automationMode", [
|
|
123
|
+
"read-only",
|
|
124
|
+
"standard",
|
|
125
|
+
"autonomous",
|
|
126
|
+
"unrestricted"
|
|
127
|
+
]),
|
|
128
|
+
enumField("browserRuntime", ["playwright", "patchright"]),
|
|
129
|
+
textField("channel"),
|
|
130
|
+
booleanField("headless"),
|
|
131
|
+
booleanField("opencliEnabled"),
|
|
132
|
+
jsonField("usagePolicy", validUsagePolicy),
|
|
133
|
+
jsonField("automationAssets", validAssetPolicy),
|
|
134
|
+
booleanField("autoInstall"),
|
|
135
|
+
textField("storageStatePath"),
|
|
136
|
+
jsonField("authProfiles"),
|
|
137
|
+
textField("defaultAuthProfile"),
|
|
138
|
+
jsonField("rulePacks"),
|
|
139
|
+
textField("executablePath"),
|
|
140
|
+
textField("snapshotDir"),
|
|
141
|
+
booleanField("verbose")
|
|
142
|
+
];
|
|
143
|
+
const SPEC_BY_FIELD = new Map(FIELD_SPECS.map((spec) => [spec.field, spec]));
|
|
144
|
+
function stable(value) {
|
|
145
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
146
|
+
if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stable(entry)}`).join(",")}}`;
|
|
147
|
+
return JSON.stringify(value);
|
|
148
|
+
}
|
|
149
|
+
function same(left, right) {
|
|
150
|
+
return stable(left) === stable(right);
|
|
151
|
+
}
|
|
152
|
+
function createLocalStore(initial) {
|
|
153
|
+
let snapshot = initial;
|
|
154
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
155
|
+
return {
|
|
156
|
+
getSnapshot: () => snapshot,
|
|
157
|
+
subscribe(listener) {
|
|
158
|
+
listeners.add(listener);
|
|
159
|
+
return () => {
|
|
160
|
+
listeners.delete(listener);
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
set(next) {
|
|
164
|
+
snapshot = next;
|
|
165
|
+
for (const listener of listeners) listener();
|
|
166
|
+
},
|
|
167
|
+
update(updater) {
|
|
168
|
+
const draft = structuredClone(snapshot);
|
|
169
|
+
updater(draft);
|
|
170
|
+
snapshot = draft;
|
|
171
|
+
for (const listener of listeners) listener();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
var BrowserSettingsController = class {
|
|
176
|
+
scope;
|
|
177
|
+
staged = /* @__PURE__ */ new Map();
|
|
178
|
+
store;
|
|
179
|
+
unsubscribe;
|
|
180
|
+
saving = false;
|
|
181
|
+
failed = false;
|
|
182
|
+
constructor(scope) {
|
|
183
|
+
this.scope = scope;
|
|
184
|
+
this.store = createLocalStore(this.project());
|
|
185
|
+
this.unsubscribe = scope.subscribe(() => {
|
|
186
|
+
this.publish();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
inject() {
|
|
190
|
+
return {
|
|
191
|
+
hooks: { browserSettings: this.store },
|
|
192
|
+
edit: (field, text) => {
|
|
193
|
+
this.edit(field, text);
|
|
194
|
+
},
|
|
195
|
+
resetField: (field) => {
|
|
196
|
+
this.resetField(field);
|
|
197
|
+
},
|
|
198
|
+
save: () => {
|
|
199
|
+
this.save();
|
|
200
|
+
},
|
|
201
|
+
discard: () => {
|
|
202
|
+
this.discard();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
snapshot() {
|
|
207
|
+
return this.store.getSnapshot();
|
|
208
|
+
}
|
|
209
|
+
edit(field, text) {
|
|
210
|
+
this.staged.set(field, {
|
|
211
|
+
text,
|
|
212
|
+
clear: false
|
|
213
|
+
});
|
|
214
|
+
this.failed = false;
|
|
215
|
+
this.publish();
|
|
216
|
+
}
|
|
217
|
+
resetField(field) {
|
|
218
|
+
const spec = this.spec(field);
|
|
219
|
+
this.staged.set(field, {
|
|
220
|
+
text: spec.format(this.baseValue(field)),
|
|
221
|
+
clear: true
|
|
222
|
+
});
|
|
223
|
+
this.failed = false;
|
|
224
|
+
this.publish();
|
|
225
|
+
}
|
|
226
|
+
discard() {
|
|
227
|
+
this.staged.clear();
|
|
228
|
+
this.failed = false;
|
|
229
|
+
this.publish();
|
|
230
|
+
}
|
|
231
|
+
async save() {
|
|
232
|
+
const plan = this.plan();
|
|
233
|
+
if (this.saving || plan.some((item) => item.write === void 0) || plan.length === 0) return;
|
|
234
|
+
this.saving = true;
|
|
235
|
+
this.failed = false;
|
|
236
|
+
this.publish();
|
|
237
|
+
let landed = true;
|
|
238
|
+
try {
|
|
239
|
+
for (const item of plan) {
|
|
240
|
+
if (!item.write) {
|
|
241
|
+
landed = false;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (item.write.kind === "clear") {
|
|
245
|
+
await this.scope.unset(item.field);
|
|
246
|
+
landed = !this.stored(item.field) && landed;
|
|
247
|
+
} else {
|
|
248
|
+
await this.scope.set(item.field, item.write.value);
|
|
249
|
+
landed = same(this.userLayer()?.[item.field], item.write.value) && landed;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
landed = false;
|
|
254
|
+
}
|
|
255
|
+
if (landed) this.staged.clear();
|
|
256
|
+
this.saving = false;
|
|
257
|
+
this.failed = !landed;
|
|
258
|
+
this.publish();
|
|
259
|
+
}
|
|
260
|
+
dispose() {
|
|
261
|
+
this.unsubscribe();
|
|
262
|
+
}
|
|
263
|
+
project() {
|
|
264
|
+
const fields = {};
|
|
265
|
+
for (const spec of FIELD_SPECS) fields[spec.field] = this.field(spec.field);
|
|
266
|
+
const plan = this.plan();
|
|
267
|
+
return {
|
|
268
|
+
available: this.scope.getSnapshot().status === "ready",
|
|
269
|
+
writable: this.scope.getSnapshot().writable,
|
|
270
|
+
dirty: plan.length > 0,
|
|
271
|
+
invalid: plan.some((item) => item.write === void 0),
|
|
272
|
+
saving: this.saving,
|
|
273
|
+
failed: this.failed,
|
|
274
|
+
fields
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
field(field) {
|
|
278
|
+
const spec = this.spec(field);
|
|
279
|
+
const draft = this.staged.get(field);
|
|
280
|
+
if (!draft) return {
|
|
281
|
+
text: spec.format(this.sectionValue(field)),
|
|
282
|
+
overridden: this.stored(field),
|
|
283
|
+
invalid: false
|
|
284
|
+
};
|
|
285
|
+
const write = draft.clear ? { kind: "clear" } : spec.parse(draft.text);
|
|
286
|
+
return {
|
|
287
|
+
text: draft.text,
|
|
288
|
+
overridden: write?.kind === "set",
|
|
289
|
+
invalid: write === void 0
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
plan() {
|
|
293
|
+
const writes = [];
|
|
294
|
+
for (const [field, draft] of this.staged) {
|
|
295
|
+
const spec = this.spec(field);
|
|
296
|
+
if (draft.clear) {
|
|
297
|
+
if (this.stored(field)) writes.push({
|
|
298
|
+
field,
|
|
299
|
+
write: { kind: "clear" }
|
|
300
|
+
});
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (draft.text === spec.format(this.sectionValue(field))) continue;
|
|
304
|
+
writes.push({
|
|
305
|
+
field,
|
|
306
|
+
write: spec.parse(draft.text)
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return writes;
|
|
310
|
+
}
|
|
311
|
+
spec(field) {
|
|
312
|
+
const spec = SPEC_BY_FIELD.get(field);
|
|
313
|
+
if (!spec) throw new Error(`unknown browser settings field: ${field}`);
|
|
314
|
+
return spec;
|
|
315
|
+
}
|
|
316
|
+
sectionValue(field) {
|
|
317
|
+
return this.scope.getSnapshot().value?.[field];
|
|
318
|
+
}
|
|
319
|
+
baseValue(field) {
|
|
320
|
+
return this.scope.getSnapshot().base?.[field];
|
|
321
|
+
}
|
|
322
|
+
userLayer() {
|
|
323
|
+
return this.scope.getSnapshot().user;
|
|
324
|
+
}
|
|
325
|
+
stored(field) {
|
|
326
|
+
const user = this.userLayer();
|
|
327
|
+
return user !== void 0 && Object.hasOwn(user, field);
|
|
328
|
+
}
|
|
329
|
+
publish() {
|
|
330
|
+
this.store.set(this.project());
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/client/styles.ts
|
|
335
|
+
const styles = {
|
|
336
|
+
card: "dsb-card",
|
|
337
|
+
open: "dsb-open",
|
|
338
|
+
header: "dsb-header",
|
|
339
|
+
head: "dsb-head",
|
|
340
|
+
titleRow: "dsb-title-row",
|
|
341
|
+
name: "dsb-name",
|
|
342
|
+
description: "dsb-description",
|
|
343
|
+
badge: "dsb-badge",
|
|
344
|
+
chevron: "dsb-chevron",
|
|
345
|
+
chevronOpen: "dsb-chevron-open",
|
|
346
|
+
body: "dsb-body",
|
|
347
|
+
notice: "dsb-notice",
|
|
348
|
+
section: "dsb-section",
|
|
349
|
+
sectionHead: "dsb-section-head",
|
|
350
|
+
grid: "dsb-grid",
|
|
351
|
+
field: "dsb-field",
|
|
352
|
+
invalid: "dsb-invalid",
|
|
353
|
+
fieldHead: "dsb-field-head",
|
|
354
|
+
label: "dsb-label",
|
|
355
|
+
hint: "dsb-hint",
|
|
356
|
+
input: "dsb-input",
|
|
357
|
+
textarea: "dsb-textarea",
|
|
358
|
+
code: "dsb-code",
|
|
359
|
+
reset: "dsb-reset",
|
|
360
|
+
toggle: "dsb-toggle",
|
|
361
|
+
toggleLabel: "dsb-toggle-label",
|
|
362
|
+
check: "dsb-check",
|
|
363
|
+
advanced: "dsb-advanced",
|
|
364
|
+
footer: "dsb-footer",
|
|
365
|
+
status: "dsb-status",
|
|
366
|
+
failed: "dsb-failed",
|
|
367
|
+
actions: "dsb-actions",
|
|
368
|
+
primary: "dsb-primary",
|
|
369
|
+
secondary: "dsb-secondary",
|
|
370
|
+
assetGroup: "dsb-asset-group",
|
|
371
|
+
assetRow: "dsb-asset-row",
|
|
372
|
+
assetToolbar: "dsb-asset-toolbar",
|
|
373
|
+
assetLayout: "dsb-asset-layout",
|
|
374
|
+
assetList: "dsb-asset-list",
|
|
375
|
+
assetItem: "dsb-asset-item",
|
|
376
|
+
assetSelected: "dsb-asset-selected",
|
|
377
|
+
assetTest: "dsb-asset-test",
|
|
378
|
+
assetTestForm: "dsb-asset-test-form",
|
|
379
|
+
assetEditor: "dsb-asset-editor",
|
|
380
|
+
invalidInput: "dsb-invalid-input"
|
|
381
|
+
};
|
|
382
|
+
function ensureStyles() {
|
|
383
|
+
if (document.getElementById("dsh-browser-settings-styles")) return;
|
|
384
|
+
const style = document.createElement("style");
|
|
385
|
+
style.id = "dsh-browser-settings-styles";
|
|
386
|
+
style.textContent = `
|
|
387
|
+
.dsb-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);transition:border-color .16s,background .16s}.dsb-card:hover{border-color:var(--dsw-alias-label-dimmed)}.dsb-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
|
|
388
|
+
.dsb-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}.dsb-header:focus-visible,.dsb-input:focus-visible,.dsb-reset:focus-visible,.dsb-primary:focus-visible,.dsb-secondary:focus-visible,.dsb-check:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
389
|
+
.dsb-head{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.dsb-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsb-name{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}.dsb-description,.dsb-hint,.dsb-section-head p{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}.dsb-badge{font-size:11px;padding:2px 7px;border-radius:9px;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
390
|
+
.dsb-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}.dsb-chevron-open{transform:rotate(180deg)}.dsb-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 8px}.dsb-notice{margin:12px 0 0;padding:9px 11px;border-radius:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3)}
|
|
391
|
+
.dsb-section{padding:18px 0}.dsb-section+.dsb-section{border-top:1px solid var(--dsw-alias-border-l2)}.dsb-section-head{margin-bottom:14px}.dsb-section-head h3{margin:0;font-size:14px;color:var(--dsw-alias-label-primary)}.dsb-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px 16px}.dsb-field{display:flex;min-width:0;flex-direction:column;gap:6px}.dsb-field-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.dsb-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
392
|
+
.dsb-input{box-sizing:border-box;width:100%;min-width:0;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;color:var(--dsw-alias-label-primary)}.dsb-input:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)}.dsb-input:disabled{opacity:.55}.dsb-invalid .dsb-input{border-color:var(--dsw-alias-label-error)}.dsb-textarea{height:auto;padding:9px 10px;resize:vertical;line-height:1.45}.dsb-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px}.dsb-reset{appearance:none;border:0;background:none;padding:0;color:var(--dsw-alias-brand-primary);font:inherit;font-size:11px;cursor:pointer}
|
|
393
|
+
.dsb-toggle{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding-top:2px}.dsb-toggle-label{display:flex;align-items:flex-start;gap:9px;cursor:pointer}.dsb-check{width:16px;height:16px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-brand-primary)}.dsb-advanced{padding:16px 0;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-advanced>summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
394
|
+
.dsb-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 0 4px;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-status,.dsb-failed{margin:0;font-size:12px}.dsb-status{color:var(--dsw-alias-label-tertiary)}.dsb-failed{color:var(--dsw-alias-label-error)}.dsb-actions{display:flex;gap:8px}.dsb-primary,.dsb-secondary{appearance:none;border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}.dsb-primary{border:1px solid transparent;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.dsb-secondary{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsb-primary:disabled,.dsb-secondary:disabled,.dsb-reset:disabled{opacity:.4;cursor:default}
|
|
395
|
+
.dsb-asset-group{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.dsb-asset-group h4{margin:0;font-size:13px}.dsb-asset-row,.dsb-asset-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px}.dsb-asset-row p,.dsb-asset-toolbar p{margin:3px 0 0;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsb-asset-toolbar{margin-bottom:10px;border:0;padding:0}.dsb-asset-layout{display:grid;grid-template-columns:minmax(180px,.7fr) minmax(0,1.3fr);gap:12px}.dsb-asset-list{display:flex;flex-direction:column;gap:6px;max-height:500px;overflow:auto}.dsb-asset-item{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.dsb-asset-item span:first-child{display:flex;min-width:0;flex-direction:column;gap:3px}.dsb-asset-item small,.dsb-asset-test{font-size:10px;color:var(--dsw-alias-label-tertiary)}.dsb-asset-selected{border-color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,var(--dsw-alias-bg-layer-3))}.dsb-asset-editor{display:flex;min-width:0;flex-direction:column;gap:8px}.dsb-asset-editor .dsb-actions{justify-content:flex-end;flex-wrap:wrap}.dsb-invalid-input{border-color:var(--dsw-alias-label-error)}
|
|
396
|
+
.dsb-asset-test-form{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px}.dsb-asset-test-form .dsb-textarea{min-height:64px}
|
|
397
|
+
@media(max-width:720px){.dsb-grid,.dsb-asset-layout{grid-template-columns:minmax(0,1fr)}.dsb-footer,.dsb-asset-row,.dsb-asset-toolbar{align-items:stretch;flex-direction:column}.dsb-actions{justify-content:flex-end}}@media(max-width:420px){.dsb-body{margin:0 12px}.dsb-actions{display:grid;grid-template-columns:1fr 1fr}.dsb-primary,.dsb-secondary{width:100%}}
|
|
398
|
+
`;
|
|
399
|
+
document.head.append(style);
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/client/SettingsCard.tsx
|
|
403
|
+
function FieldShell(props) {
|
|
404
|
+
const id = `dsh-browser-${props.field}`;
|
|
405
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
406
|
+
className: `${styles.field} ${props.state.invalid ? styles.invalid : ""}`,
|
|
407
|
+
children: [
|
|
408
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
409
|
+
className: styles.fieldHead,
|
|
410
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
411
|
+
className: styles.label,
|
|
412
|
+
htmlFor: id,
|
|
413
|
+
children: props.label
|
|
414
|
+
}), props.state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
415
|
+
type: "button",
|
|
416
|
+
className: styles.reset,
|
|
417
|
+
disabled: props.disabled,
|
|
418
|
+
onClick: () => props.onReset(props.field),
|
|
419
|
+
children: props.resetLabel
|
|
420
|
+
}) : null]
|
|
421
|
+
}),
|
|
422
|
+
props.children,
|
|
423
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
424
|
+
className: styles.hint,
|
|
425
|
+
children: props.hint
|
|
426
|
+
})
|
|
427
|
+
]
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function SettingsCard(props) {
|
|
431
|
+
const { t } = props;
|
|
432
|
+
const state = props.useBrowserSettings((snapshot) => snapshot);
|
|
433
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
434
|
+
if (!state.available) return null;
|
|
435
|
+
const disabled = !state.writable || state.saving;
|
|
436
|
+
const text = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
|
|
437
|
+
field,
|
|
438
|
+
state: state.fields[field],
|
|
439
|
+
label: t(label),
|
|
440
|
+
hint: state.fields[field].invalid ? t("invalid") : t(hint),
|
|
441
|
+
disabled,
|
|
442
|
+
resetLabel: t("reset"),
|
|
443
|
+
onReset: props.resetField,
|
|
444
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
445
|
+
id: `dsh-browser-${field}`,
|
|
446
|
+
className: styles.input,
|
|
447
|
+
value: state.fields[field].text,
|
|
448
|
+
disabled,
|
|
449
|
+
"aria-invalid": state.fields[field].invalid || void 0,
|
|
450
|
+
onChange: (event) => props.edit(field, event.currentTarget.value)
|
|
451
|
+
})
|
|
452
|
+
});
|
|
453
|
+
const select = (field, label, hint, options) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
|
|
454
|
+
field,
|
|
455
|
+
state: state.fields[field],
|
|
456
|
+
label: t(label),
|
|
457
|
+
hint: state.fields[field].invalid ? t("invalid") : t(hint),
|
|
458
|
+
disabled,
|
|
459
|
+
resetLabel: t("reset"),
|
|
460
|
+
onReset: props.resetField,
|
|
461
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
462
|
+
id: `dsh-browser-${field}`,
|
|
463
|
+
className: styles.input,
|
|
464
|
+
value: state.fields[field].text,
|
|
465
|
+
disabled,
|
|
466
|
+
onChange: (event) => props.edit(field, event.currentTarget.value),
|
|
467
|
+
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
468
|
+
value: option,
|
|
469
|
+
children: option
|
|
470
|
+
}, option))
|
|
471
|
+
})
|
|
472
|
+
});
|
|
473
|
+
const json = (field, label, hint, rows = 7) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
|
|
474
|
+
field,
|
|
475
|
+
state: state.fields[field],
|
|
476
|
+
label: t(label),
|
|
477
|
+
hint: state.fields[field].invalid ? t("invalidJson") : t(hint),
|
|
478
|
+
disabled,
|
|
479
|
+
resetLabel: t("reset"),
|
|
480
|
+
onReset: props.resetField,
|
|
481
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
482
|
+
id: `dsh-browser-${field}`,
|
|
483
|
+
className: `${styles.input} ${styles.textarea} ${styles.code}`,
|
|
484
|
+
rows,
|
|
485
|
+
value: state.fields[field].text,
|
|
486
|
+
disabled,
|
|
487
|
+
spellCheck: false,
|
|
488
|
+
"aria-invalid": state.fields[field].invalid || void 0,
|
|
489
|
+
onChange: (event) => props.edit(field, event.currentTarget.value)
|
|
490
|
+
})
|
|
491
|
+
});
|
|
492
|
+
const toggle = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
493
|
+
className: styles.toggle,
|
|
494
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
495
|
+
className: styles.toggleLabel,
|
|
496
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
497
|
+
className: styles.check,
|
|
498
|
+
type: "checkbox",
|
|
499
|
+
checked: state.fields[field].text === "true",
|
|
500
|
+
disabled,
|
|
501
|
+
onChange: (event) => props.edit(field, String(event.currentTarget.checked))
|
|
502
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
503
|
+
className: styles.label,
|
|
504
|
+
children: t(label)
|
|
505
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
506
|
+
className: styles.hint,
|
|
507
|
+
children: t(hint)
|
|
508
|
+
})] })]
|
|
509
|
+
}), state.fields[field].overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
510
|
+
type: "button",
|
|
511
|
+
className: styles.reset,
|
|
512
|
+
disabled,
|
|
513
|
+
onClick: () => props.resetField(field),
|
|
514
|
+
children: t("reset")
|
|
515
|
+
}) : null]
|
|
516
|
+
});
|
|
517
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
518
|
+
className: `${styles.card} ${open ? styles.open : ""}`,
|
|
519
|
+
"data-dsh-browser-settings": true,
|
|
520
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
521
|
+
type: "button",
|
|
522
|
+
className: styles.header,
|
|
523
|
+
"aria-expanded": open,
|
|
524
|
+
"aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
|
|
525
|
+
onClick: () => setOpen(!open),
|
|
526
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
527
|
+
className: styles.head,
|
|
528
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
529
|
+
className: styles.titleRow,
|
|
530
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
531
|
+
className: styles.name,
|
|
532
|
+
children: t("title")
|
|
533
|
+
}), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
534
|
+
className: styles.badge,
|
|
535
|
+
children: t("unsaved")
|
|
536
|
+
}) : null]
|
|
537
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
538
|
+
className: styles.description,
|
|
539
|
+
children: t("description")
|
|
540
|
+
})]
|
|
541
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
542
|
+
className: `${styles.chevron} ${open ? styles.chevronOpen : ""}`,
|
|
543
|
+
viewBox: "0 0 14 14",
|
|
544
|
+
width: "14",
|
|
545
|
+
height: "14",
|
|
546
|
+
"aria-hidden": "true",
|
|
547
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
548
|
+
d: "M3.5 5.5 7 9l3.5-3.5",
|
|
549
|
+
fill: "none",
|
|
550
|
+
stroke: "currentColor",
|
|
551
|
+
strokeWidth: "1.5"
|
|
552
|
+
})
|
|
553
|
+
})]
|
|
554
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
555
|
+
className: styles.body,
|
|
556
|
+
children: [
|
|
557
|
+
!state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
558
|
+
className: styles.notice,
|
|
559
|
+
role: "status",
|
|
560
|
+
children: t("readOnly")
|
|
561
|
+
}) : null,
|
|
562
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
563
|
+
className: styles.section,
|
|
564
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
565
|
+
className: styles.sectionHead,
|
|
566
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("freedom") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("freedomHint") })]
|
|
567
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
568
|
+
className: styles.grid,
|
|
569
|
+
children: [
|
|
570
|
+
toggle("enabled", "enabled", "enabledHint"),
|
|
571
|
+
select("automationMode", "automationMode", "automationModeHint", [
|
|
572
|
+
"read-only",
|
|
573
|
+
"standard",
|
|
574
|
+
"autonomous",
|
|
575
|
+
"unrestricted"
|
|
576
|
+
]),
|
|
577
|
+
toggle("opencliEnabled", "opencliEnabled", "opencliEnabledHint")
|
|
578
|
+
]
|
|
579
|
+
})]
|
|
580
|
+
}),
|
|
581
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
582
|
+
className: styles.section,
|
|
583
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
584
|
+
className: styles.sectionHead,
|
|
585
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("runtime") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("runtimeHint") })]
|
|
586
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
587
|
+
className: styles.grid,
|
|
588
|
+
children: [
|
|
589
|
+
select("browserRuntime", "browserRuntime", "browserRuntimeHint", ["playwright", "patchright"]),
|
|
590
|
+
text("channel", "channel", "channelHint"),
|
|
591
|
+
toggle("headless", "headless", "headlessHint"),
|
|
592
|
+
toggle("autoInstall", "autoInstall", "autoInstallHint"),
|
|
593
|
+
text("executablePath", "executablePath", "executablePathHint")
|
|
594
|
+
]
|
|
595
|
+
})]
|
|
596
|
+
}),
|
|
597
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
598
|
+
className: styles.section,
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
601
|
+
className: styles.sectionHead,
|
|
602
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("usageHint") })]
|
|
603
|
+
}),
|
|
604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
605
|
+
className: styles.grid,
|
|
606
|
+
children: [json("usagePolicy", "usagePolicy", "usagePolicyHint", 10), json("automationAssets", "automationAssets", "automationAssetsHint", 14)]
|
|
607
|
+
}),
|
|
608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
609
|
+
className: styles.notice,
|
|
610
|
+
role: "note",
|
|
611
|
+
children: t("restart")
|
|
612
|
+
})
|
|
613
|
+
]
|
|
614
|
+
}),
|
|
615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutomationAssetsPanel, { ...props }),
|
|
616
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
617
|
+
className: styles.advanced,
|
|
618
|
+
children: [
|
|
619
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }),
|
|
620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
621
|
+
className: styles.hint,
|
|
622
|
+
children: t("advancedHint")
|
|
623
|
+
}),
|
|
624
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
625
|
+
className: styles.grid,
|
|
626
|
+
children: [
|
|
627
|
+
text("storageStatePath", "storageStatePath", "storageStatePathHint"),
|
|
628
|
+
text("defaultAuthProfile", "defaultAuthProfile", "defaultAuthProfileHint"),
|
|
629
|
+
json("authProfiles", "authProfiles", "authProfilesHint"),
|
|
630
|
+
json("rulePacks", "rulePacks", "rulePacksHint"),
|
|
631
|
+
text("snapshotDir", "snapshotDir", "snapshotDirHint"),
|
|
632
|
+
toggle("verbose", "verbose", "verboseHint")
|
|
633
|
+
]
|
|
634
|
+
})
|
|
635
|
+
]
|
|
636
|
+
}),
|
|
637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
638
|
+
className: styles.footer,
|
|
639
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
640
|
+
className: state.failed ? styles.failed : styles.status,
|
|
641
|
+
role: "status",
|
|
642
|
+
"aria-live": "polite",
|
|
643
|
+
children: t(state.failed ? "saveFailed" : state.invalid ? "invalidSave" : state.dirty ? "pending" : "saved")
|
|
644
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
645
|
+
className: styles.actions,
|
|
646
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
647
|
+
type: "button",
|
|
648
|
+
className: styles.secondary,
|
|
649
|
+
disabled: !state.dirty || state.saving,
|
|
650
|
+
onClick: props.discard,
|
|
651
|
+
children: t("discard")
|
|
652
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
|
+
type: "button",
|
|
654
|
+
className: styles.primary,
|
|
655
|
+
disabled: !state.dirty || state.invalid || state.saving || !state.writable,
|
|
656
|
+
onClick: props.save,
|
|
657
|
+
children: t(state.saving ? "saving" : "save")
|
|
658
|
+
})]
|
|
659
|
+
})]
|
|
660
|
+
})
|
|
661
|
+
]
|
|
662
|
+
}) : null]
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function AutomationAssetsPanel(props) {
|
|
666
|
+
const { t } = props;
|
|
667
|
+
const state = props.useAutomationAssets((snapshot) => snapshot);
|
|
668
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
669
|
+
const [draftError, setDraftError] = (0, react.useState)(false);
|
|
670
|
+
const [testUrl, setTestUrl] = (0, react.useState)("");
|
|
671
|
+
const [testInputs, setTestInputs] = (0, react.useState)("{}");
|
|
672
|
+
const selected = state.selected;
|
|
673
|
+
(0, react.useEffect)(() => {
|
|
674
|
+
if (selected) setDraft(JSON.stringify(selected, null, 2));
|
|
675
|
+
}, [selected?.id, selected?.revision]);
|
|
676
|
+
const newAsset = (kind) => {
|
|
677
|
+
props.selectAutomationAsset(void 0);
|
|
678
|
+
setDraft(JSON.stringify(kind === "recipe" ? {
|
|
679
|
+
kind,
|
|
680
|
+
name: "New recipe",
|
|
681
|
+
description: "",
|
|
682
|
+
domains: [],
|
|
683
|
+
tags: [],
|
|
684
|
+
inputNames: [],
|
|
685
|
+
recipe: [{
|
|
686
|
+
type: "extract",
|
|
687
|
+
selector: "main",
|
|
688
|
+
mode: "text",
|
|
689
|
+
limit: 20
|
|
690
|
+
}]
|
|
691
|
+
} : {
|
|
692
|
+
kind,
|
|
693
|
+
name: "New userscript",
|
|
694
|
+
description: "",
|
|
695
|
+
domains: [],
|
|
696
|
+
tags: [],
|
|
697
|
+
inputNames: [],
|
|
698
|
+
source: "// ==UserScript==\n// @name New userscript\n// @match https://example.com/*\n// @grant none\n// ==/UserScript==\nreturn { title: document.title }"
|
|
699
|
+
}, null, 2));
|
|
700
|
+
setDraftError(false);
|
|
701
|
+
};
|
|
702
|
+
const save = async () => {
|
|
703
|
+
try {
|
|
704
|
+
const value = JSON.parse(draft);
|
|
705
|
+
if (selected?.id) value.id = selected.id;
|
|
706
|
+
await props.saveAutomationAsset(value);
|
|
707
|
+
setDraftError(false);
|
|
708
|
+
} catch {
|
|
709
|
+
setDraftError(true);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const runTest = async () => {
|
|
713
|
+
if (!selected || !testUrl.trim()) {
|
|
714
|
+
setDraftError(true);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const inputs = JSON.parse(testInputs);
|
|
719
|
+
await props.testAutomationAsset(selected.id, testUrl.trim(), inputs);
|
|
720
|
+
setDraftError(false);
|
|
721
|
+
} catch {
|
|
722
|
+
setDraftError(true);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
const candidates = state.snapshot?.candidates.filter((candidate) => candidate.suggestedAt && !candidate.dismissedAt) ?? [];
|
|
726
|
+
const assets = state.snapshot?.assets ?? [];
|
|
727
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
728
|
+
className: styles.section,
|
|
729
|
+
"data-dsh-browser-assets": true,
|
|
730
|
+
children: [
|
|
731
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
732
|
+
className: styles.sectionHead,
|
|
733
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("assetLibrary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("assetLibraryHint") })]
|
|
734
|
+
}),
|
|
735
|
+
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
736
|
+
className: styles.notice,
|
|
737
|
+
children: t("assetLoading")
|
|
738
|
+
}) : null,
|
|
739
|
+
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
740
|
+
className: styles.failed,
|
|
741
|
+
role: "alert",
|
|
742
|
+
children: state.error || t("assetFailed")
|
|
743
|
+
}) : null,
|
|
744
|
+
candidates.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
745
|
+
className: styles.assetGroup,
|
|
746
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("assetSuggestions") }), candidates.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
747
|
+
className: styles.assetRow,
|
|
748
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: candidate.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
749
|
+
candidate.domain,
|
|
750
|
+
" · ",
|
|
751
|
+
candidate.successfulRuns,
|
|
752
|
+
" ",
|
|
753
|
+
t("assetRuns"),
|
|
754
|
+
" · ",
|
|
755
|
+
candidate.distinctSessions,
|
|
756
|
+
" ",
|
|
757
|
+
t("assetSessions")
|
|
758
|
+
] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
759
|
+
className: styles.actions,
|
|
760
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
761
|
+
type: "button",
|
|
762
|
+
className: styles.secondary,
|
|
763
|
+
disabled: state.busy,
|
|
764
|
+
onClick: () => props.dismissAutomationCandidate(candidate.id),
|
|
765
|
+
children: t("assetDismiss")
|
|
766
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
767
|
+
type: "button",
|
|
768
|
+
className: styles.primary,
|
|
769
|
+
disabled: state.busy,
|
|
770
|
+
onClick: () => props.summarizeAutomationCandidate(candidate.id),
|
|
771
|
+
children: t("assetSummarize")
|
|
772
|
+
})]
|
|
773
|
+
})]
|
|
774
|
+
}, candidate.id))]
|
|
775
|
+
}) : null,
|
|
776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
777
|
+
className: styles.assetToolbar,
|
|
778
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("assetScripts") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
779
|
+
className: styles.hint,
|
|
780
|
+
children: t("assetScriptsHint")
|
|
781
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
782
|
+
className: styles.actions,
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
785
|
+
type: "button",
|
|
786
|
+
className: styles.secondary,
|
|
787
|
+
onClick: () => newAsset("recipe"),
|
|
788
|
+
children: t("assetNewRecipe")
|
|
789
|
+
}),
|
|
790
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
791
|
+
type: "button",
|
|
792
|
+
className: styles.secondary,
|
|
793
|
+
onClick: () => newAsset("userscript"),
|
|
794
|
+
children: t("assetNewScript")
|
|
795
|
+
}),
|
|
796
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
797
|
+
type: "button",
|
|
798
|
+
className: styles.secondary,
|
|
799
|
+
onClick: props.refreshAutomationAssets,
|
|
800
|
+
children: t("assetRefresh")
|
|
801
|
+
})
|
|
802
|
+
]
|
|
803
|
+
})]
|
|
804
|
+
}),
|
|
805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
806
|
+
className: styles.assetLayout,
|
|
807
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
808
|
+
className: styles.assetList,
|
|
809
|
+
children: assets.length ? assets.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
810
|
+
type: "button",
|
|
811
|
+
className: `${styles.assetItem} ${selected?.id === asset.id ? styles.assetSelected : ""}`,
|
|
812
|
+
onClick: () => props.selectAutomationAsset(asset.id),
|
|
813
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: asset.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", { children: [
|
|
814
|
+
asset.kind,
|
|
815
|
+
" · ",
|
|
816
|
+
asset.status,
|
|
817
|
+
" · r",
|
|
818
|
+
asset.revision
|
|
819
|
+
] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
820
|
+
className: styles.assetTest,
|
|
821
|
+
children: asset.testStatus
|
|
822
|
+
})]
|
|
823
|
+
}, asset.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
824
|
+
className: styles.hint,
|
|
825
|
+
children: t("assetEmpty")
|
|
826
|
+
})
|
|
827
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
828
|
+
className: styles.assetEditor,
|
|
829
|
+
children: [
|
|
830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
831
|
+
className: styles.label,
|
|
832
|
+
htmlFor: "dsh-browser-asset-editor",
|
|
833
|
+
children: t("assetEditor")
|
|
834
|
+
}),
|
|
835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
836
|
+
id: "dsh-browser-asset-editor",
|
|
837
|
+
className: `${styles.input} ${styles.textarea} ${styles.code} ${draftError ? styles.invalidInput : ""}`,
|
|
838
|
+
rows: 18,
|
|
839
|
+
value: draft,
|
|
840
|
+
spellCheck: false,
|
|
841
|
+
placeholder: t("assetEditorHint"),
|
|
842
|
+
onChange: (event) => {
|
|
843
|
+
setDraft(event.currentTarget.value);
|
|
844
|
+
setDraftError(false);
|
|
845
|
+
}
|
|
846
|
+
}),
|
|
847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
848
|
+
className: styles.hint,
|
|
849
|
+
children: draftError ? t("assetInvalid") : t("assetSourceBoundary")
|
|
850
|
+
}),
|
|
851
|
+
selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
852
|
+
className: styles.assetTestForm,
|
|
853
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
854
|
+
className: styles.input,
|
|
855
|
+
value: testUrl,
|
|
856
|
+
placeholder: t("assetTestUrl"),
|
|
857
|
+
onChange: (event) => setTestUrl(event.currentTarget.value)
|
|
858
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
859
|
+
className: `${styles.input} ${styles.textarea} ${styles.code}`,
|
|
860
|
+
rows: 3,
|
|
861
|
+
value: testInputs,
|
|
862
|
+
spellCheck: false,
|
|
863
|
+
"aria-label": t("assetTestInputs"),
|
|
864
|
+
onChange: (event) => setTestInputs(event.currentTarget.value)
|
|
865
|
+
})]
|
|
866
|
+
}) : null,
|
|
867
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
868
|
+
className: styles.actions,
|
|
869
|
+
children: [
|
|
870
|
+
selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
871
|
+
type: "button",
|
|
872
|
+
className: styles.secondary,
|
|
873
|
+
disabled: state.busy,
|
|
874
|
+
onClick: () => props.validateAutomationAsset(selected.id),
|
|
875
|
+
children: t("assetValidate")
|
|
876
|
+
}) : null,
|
|
877
|
+
selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
878
|
+
type: "button",
|
|
879
|
+
className: styles.secondary,
|
|
880
|
+
disabled: state.busy || !testUrl.trim(),
|
|
881
|
+
onClick: () => void runTest(),
|
|
882
|
+
children: t("assetTest")
|
|
883
|
+
}) : null,
|
|
884
|
+
selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
885
|
+
type: "button",
|
|
886
|
+
className: styles.secondary,
|
|
887
|
+
disabled: state.busy || selected.testStatus !== "passed",
|
|
888
|
+
onClick: () => props.setAutomationAssetStatus(selected.id, "active"),
|
|
889
|
+
children: t("assetActivate")
|
|
890
|
+
}) : null,
|
|
891
|
+
selected && selected.status !== "archived" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
892
|
+
type: "button",
|
|
893
|
+
className: styles.secondary,
|
|
894
|
+
disabled: state.busy,
|
|
895
|
+
onClick: () => props.setAutomationAssetStatus(selected.id, "archived"),
|
|
896
|
+
children: t("assetArchive")
|
|
897
|
+
}) : null,
|
|
898
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
899
|
+
type: "button",
|
|
900
|
+
className: styles.primary,
|
|
901
|
+
disabled: state.busy || !draft || selected?.status === "active",
|
|
902
|
+
onClick: () => void save(),
|
|
903
|
+
children: t("assetSaveDraft")
|
|
904
|
+
})
|
|
905
|
+
]
|
|
906
|
+
})
|
|
907
|
+
]
|
|
908
|
+
})]
|
|
909
|
+
})
|
|
910
|
+
]
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/client/locales.ts
|
|
915
|
+
const zh = {
|
|
916
|
+
title: "浏览器自动化",
|
|
917
|
+
description: "运行时、工具自由度、OpenCLI 与防止过度调用的缓冲策略。",
|
|
918
|
+
expand: "展开设置",
|
|
919
|
+
collapse: "收起设置",
|
|
920
|
+
unsaved: "未保存",
|
|
921
|
+
readOnly: "当前配置只读。",
|
|
922
|
+
freedom: "自动化自由度",
|
|
923
|
+
freedomHint: "无审批模式只跳过人工确认,不会绕过调用缓冲和参数校验。",
|
|
924
|
+
runtime: "浏览器运行时",
|
|
925
|
+
runtimeHint: "默认 Playwright;遇到兼容性检测时可显式切换 Patchright。",
|
|
926
|
+
usage: "使用策略缓冲",
|
|
927
|
+
usageHint: "限制并发、短时突发、爬取预算,并对 429/503 等响应退避。",
|
|
928
|
+
advanced: "登录态与高级设置",
|
|
929
|
+
advancedHint: "状态文件不要提交到仓库;AuthProfile 必须配置 allowedDomains。",
|
|
930
|
+
enabled: "启用浏览器服务",
|
|
931
|
+
enabledHint: "关闭后浏览器服务不可用。",
|
|
932
|
+
automationMode: "自动化模式",
|
|
933
|
+
automationModeHint: "read-only / standard / autonomous / unrestricted。",
|
|
934
|
+
browserRuntime: "运行时提供器",
|
|
935
|
+
browserRuntimeHint: "Patchright 仅支持 Chromium。",
|
|
936
|
+
channel: "浏览器通道",
|
|
937
|
+
channelHint: "chromium、chrome 或 msedge。Patchright 推荐 chrome。",
|
|
938
|
+
headless: "无头模式",
|
|
939
|
+
headlessHint: "Patchright 兼容性最佳配置通常是关闭无头模式。",
|
|
940
|
+
opencliEnabled: "启用 OpenCLI",
|
|
941
|
+
opencliEnabledHint: "站点 adapter 和 Chrome Browser Bridge 总开关。",
|
|
942
|
+
usagePolicy: "调用缓冲 JSON",
|
|
943
|
+
usagePolicyHint: "minDelayMs、maxConcurrency、burst、maxPagesPerRun、maxDepth、retryLimit、backoffBaseMs、cooldownMs。",
|
|
944
|
+
automationAssets: "自动化资产策略 JSON",
|
|
945
|
+
automationAssetsHint: "候选阈值、持久化模式、激活模式、数量与上下文预算。",
|
|
946
|
+
assetLibrary: "可复用自动化资产(实验性)",
|
|
947
|
+
assetLibraryHint: "实验功能:候选先提示是否总结;草稿真实回放后再手动激活。源码仅在点选编辑时读取。",
|
|
948
|
+
assetLoading: "正在读取本地自动化资产…",
|
|
949
|
+
assetFailed: "自动化资产读取失败。",
|
|
950
|
+
assetSuggestions: "建议总结",
|
|
951
|
+
assetRuns: "次成功",
|
|
952
|
+
assetSessions: "个会话",
|
|
953
|
+
assetDismiss: "暂不总结",
|
|
954
|
+
assetSummarize: "总结为草稿",
|
|
955
|
+
assetScripts: "脚本与 recipe",
|
|
956
|
+
assetScriptsHint: "模型只能检索已激活资产的有界摘要。",
|
|
957
|
+
assetNewRecipe: "新建 recipe",
|
|
958
|
+
assetNewScript: "新建油猴脚本",
|
|
959
|
+
assetRefresh: "刷新",
|
|
960
|
+
assetEmpty: "还没有资产。",
|
|
961
|
+
assetEditor: "资产编辑器(JSON)",
|
|
962
|
+
assetEditorHint: "选择资产或新建草稿。",
|
|
963
|
+
assetInvalid: "JSON、测试 URL 或输入无效。",
|
|
964
|
+
assetSourceBoundary: "不要保存 cookie、token、密码、完整页面内容或聊天记录。",
|
|
965
|
+
assetValidate: "静态校验",
|
|
966
|
+
assetTest: "真实回放",
|
|
967
|
+
assetTestUrl: "测试 URL(必须命中允许域名)",
|
|
968
|
+
assetTestInputs: "测试输入 JSON",
|
|
969
|
+
assetActivate: "激活",
|
|
970
|
+
assetArchive: "归档",
|
|
971
|
+
assetSaveDraft: "保存草稿",
|
|
972
|
+
autoInstall: "缺失时自动安装 Chromium",
|
|
973
|
+
autoInstallHint: "可能触发较大下载,日常建议关闭并显式调用 browser_install。",
|
|
974
|
+
storageStatePath: "全局 storageState 路径",
|
|
975
|
+
storageStatePathHint: "旧版兼容入口;新配置优先使用限域 AuthProfile。",
|
|
976
|
+
authProfiles: "AuthProfiles JSON",
|
|
977
|
+
authProfilesHint: "命名登录态、allowedDomains 与 persistState。",
|
|
978
|
+
defaultAuthProfile: "默认 AuthProfile",
|
|
979
|
+
defaultAuthProfileHint: "未显式选择登录态时使用;browser_crawl 始终匿名。",
|
|
980
|
+
rulePacks: "RulePacks JSON",
|
|
981
|
+
rulePacksHint: "域名匹配、哈希固定 init script 和有界步骤。",
|
|
982
|
+
executablePath: "浏览器可执行文件",
|
|
983
|
+
executablePathHint: "少数自定义部署才需要覆盖。",
|
|
984
|
+
snapshotDir: "快照目录",
|
|
985
|
+
snapshotDirHint: "留空使用 DSH_HOME 下的默认目录。",
|
|
986
|
+
verbose: "详细日志",
|
|
987
|
+
verboseHint: "输出启动和诊断信息。",
|
|
988
|
+
reset: "恢复部署值",
|
|
989
|
+
invalid: "值无效,请检查格式或范围。",
|
|
990
|
+
invalidJson: "JSON 或数值范围无效。",
|
|
991
|
+
restart: "保存后完整重启 profile,运行时和工具目录才会重新注册。",
|
|
992
|
+
saved: "配置已同步。",
|
|
993
|
+
pending: "有待保存的修改。",
|
|
994
|
+
invalidSave: "存在无效字段。",
|
|
995
|
+
saveFailed: "保存失败,草稿已保留。",
|
|
996
|
+
saving: "保存中…",
|
|
997
|
+
save: "保存",
|
|
998
|
+
discard: "放弃修改"
|
|
999
|
+
};
|
|
1000
|
+
const en = {
|
|
1001
|
+
title: "Browser automation",
|
|
1002
|
+
description: "Runtime, tool freedom, OpenCLI, and overuse buffering policy.",
|
|
1003
|
+
expand: "Expand settings",
|
|
1004
|
+
collapse: "Collapse settings",
|
|
1005
|
+
unsaved: "Unsaved",
|
|
1006
|
+
readOnly: "Configuration is read-only.",
|
|
1007
|
+
freedom: "Automation freedom",
|
|
1008
|
+
freedomHint: "No-approval skips human confirmation only; buffering and validation remain active.",
|
|
1009
|
+
runtime: "Browser runtime",
|
|
1010
|
+
runtimeHint: "Playwright by default; explicitly select Patchright for compatibility-sensitive sites.",
|
|
1011
|
+
usage: "Usage buffer",
|
|
1012
|
+
usageHint: "Bounds concurrency, bursts and crawl budgets, with backoff for 429/503 responses.",
|
|
1013
|
+
advanced: "Auth and advanced settings",
|
|
1014
|
+
advancedHint: "Never commit state files; AuthProfiles must declare allowedDomains.",
|
|
1015
|
+
enabled: "Enable browser service",
|
|
1016
|
+
enabledHint: "Disabling makes the browser service unavailable.",
|
|
1017
|
+
automationMode: "Automation mode",
|
|
1018
|
+
automationModeHint: "read-only / standard / autonomous / unrestricted.",
|
|
1019
|
+
browserRuntime: "Runtime provider",
|
|
1020
|
+
browserRuntimeHint: "Patchright is Chromium-only.",
|
|
1021
|
+
channel: "Browser channel",
|
|
1022
|
+
channelHint: "chromium, chrome, or msedge. Patchright recommends chrome.",
|
|
1023
|
+
headless: "Headless mode",
|
|
1024
|
+
headlessHint: "Patchright compatibility is usually strongest in headed mode.",
|
|
1025
|
+
opencliEnabled: "Enable OpenCLI",
|
|
1026
|
+
opencliEnabledHint: "Master switch for site adapters and the Chrome Browser Bridge.",
|
|
1027
|
+
usagePolicy: "Usage buffer JSON",
|
|
1028
|
+
usagePolicyHint: "minDelayMs, maxConcurrency, burst, maxPagesPerRun, maxDepth, retryLimit, backoffBaseMs, cooldownMs.",
|
|
1029
|
+
automationAssets: "Automation asset policy JSON",
|
|
1030
|
+
automationAssetsHint: "Candidate thresholds, persistence, activation, count, and context budgets.",
|
|
1031
|
+
assetLibrary: "Reusable automation assets (Experimental)",
|
|
1032
|
+
assetLibraryHint: "Experimental: candidates ask before summarization; drafts require runtime replay before manual activation. Source loads only when selected.",
|
|
1033
|
+
assetLoading: "Loading local automation assets…",
|
|
1034
|
+
assetFailed: "Failed to load automation assets.",
|
|
1035
|
+
assetSuggestions: "Summarization suggestions",
|
|
1036
|
+
assetRuns: "successful runs",
|
|
1037
|
+
assetSessions: "sessions",
|
|
1038
|
+
assetDismiss: "Not now",
|
|
1039
|
+
assetSummarize: "Summarize to draft",
|
|
1040
|
+
assetScripts: "Scripts and recipes",
|
|
1041
|
+
assetScriptsHint: "The model can retrieve only bounded summaries of active assets.",
|
|
1042
|
+
assetNewRecipe: "New recipe",
|
|
1043
|
+
assetNewScript: "New userscript",
|
|
1044
|
+
assetRefresh: "Refresh",
|
|
1045
|
+
assetEmpty: "No assets yet.",
|
|
1046
|
+
assetEditor: "Asset editor (JSON)",
|
|
1047
|
+
assetEditorHint: "Select an asset or create a draft.",
|
|
1048
|
+
assetInvalid: "Invalid JSON, test URL, or inputs.",
|
|
1049
|
+
assetSourceBoundary: "Do not store cookies, tokens, passwords, full page content, or conversation transcripts.",
|
|
1050
|
+
assetValidate: "Static validate",
|
|
1051
|
+
assetTest: "Runtime replay",
|
|
1052
|
+
assetTestUrl: "Test URL (must match an allowed domain)",
|
|
1053
|
+
assetTestInputs: "Test inputs JSON",
|
|
1054
|
+
assetActivate: "Activate",
|
|
1055
|
+
assetArchive: "Archive",
|
|
1056
|
+
assetSaveDraft: "Save draft",
|
|
1057
|
+
autoInstall: "Auto-install Chromium when missing",
|
|
1058
|
+
autoInstallHint: "May download a large binary; explicit browser_install is safer for daily use.",
|
|
1059
|
+
storageStatePath: "Global storageState path",
|
|
1060
|
+
storageStatePathHint: "Legacy fallback; prefer domain-scoped AuthProfiles.",
|
|
1061
|
+
authProfiles: "AuthProfiles JSON",
|
|
1062
|
+
authProfilesHint: "Named states with allowedDomains and persistState.",
|
|
1063
|
+
defaultAuthProfile: "Default AuthProfile",
|
|
1064
|
+
defaultAuthProfileHint: "Used when no profile is selected; browser_crawl is always anonymous.",
|
|
1065
|
+
rulePacks: "RulePacks JSON",
|
|
1066
|
+
rulePacksHint: "Domain match, hash-pinned init scripts, and bounded steps.",
|
|
1067
|
+
executablePath: "Browser executable",
|
|
1068
|
+
executablePathHint: "Override only for custom deployments.",
|
|
1069
|
+
snapshotDir: "Snapshot directory",
|
|
1070
|
+
snapshotDirHint: "Leave empty for the DSH_HOME default.",
|
|
1071
|
+
verbose: "Verbose logs",
|
|
1072
|
+
verboseHint: "Emit startup and diagnostic details.",
|
|
1073
|
+
reset: "Restore deployed",
|
|
1074
|
+
invalid: "Invalid value. Check format and bounds.",
|
|
1075
|
+
invalidJson: "Invalid JSON or numeric bounds.",
|
|
1076
|
+
restart: "Fully restart the profile after saving so runtime and tool catalogs are re-registered.",
|
|
1077
|
+
saved: "Configuration is synchronized.",
|
|
1078
|
+
pending: "Changes are ready to save.",
|
|
1079
|
+
invalidSave: "Some fields are invalid.",
|
|
1080
|
+
saveFailed: "Save failed; the draft was retained.",
|
|
1081
|
+
saving: "Saving…",
|
|
1082
|
+
save: "Save",
|
|
1083
|
+
discard: "Discard"
|
|
1084
|
+
};
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region src/client/settings-namespace.ts
|
|
1087
|
+
/** Settings namespace used by both the Host registry and the card slot key. */
|
|
1088
|
+
const SETTINGS_NAMESPACE = "browser";
|
|
1089
|
+
//#endregion
|
|
1090
|
+
//#region src/client/automation-assets-client.ts
|
|
1091
|
+
function localStore(initial) {
|
|
1092
|
+
let snapshot = initial;
|
|
1093
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1094
|
+
return {
|
|
1095
|
+
getSnapshot: () => snapshot,
|
|
1096
|
+
subscribe(listener) {
|
|
1097
|
+
listeners.add(listener);
|
|
1098
|
+
return () => {
|
|
1099
|
+
listeners.delete(listener);
|
|
1100
|
+
};
|
|
1101
|
+
},
|
|
1102
|
+
set(next) {
|
|
1103
|
+
snapshot = next;
|
|
1104
|
+
for (const listener of listeners) listener();
|
|
1105
|
+
},
|
|
1106
|
+
update(updater) {
|
|
1107
|
+
const draft = structuredClone(snapshot);
|
|
1108
|
+
updater(draft);
|
|
1109
|
+
snapshot = draft;
|
|
1110
|
+
for (const listener of listeners) listener();
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
var AutomationAssetsController = class {
|
|
1115
|
+
rpc;
|
|
1116
|
+
store = localStore({
|
|
1117
|
+
loading: true,
|
|
1118
|
+
busy: false,
|
|
1119
|
+
failed: false
|
|
1120
|
+
});
|
|
1121
|
+
disposed = false;
|
|
1122
|
+
constructor(rpc) {
|
|
1123
|
+
this.rpc = rpc;
|
|
1124
|
+
this.refresh();
|
|
1125
|
+
}
|
|
1126
|
+
inject() {
|
|
1127
|
+
return {
|
|
1128
|
+
hooks: { automationAssets: this.store },
|
|
1129
|
+
refreshAutomationAssets: () => {
|
|
1130
|
+
this.refresh();
|
|
1131
|
+
},
|
|
1132
|
+
selectAutomationAsset: (id) => {
|
|
1133
|
+
this.select(id);
|
|
1134
|
+
},
|
|
1135
|
+
saveAutomationAsset: (asset) => this.mutate("save", { asset }),
|
|
1136
|
+
summarizeAutomationCandidate: (id) => this.mutate("summarize", { id }),
|
|
1137
|
+
dismissAutomationCandidate: (id) => this.mutate("dismiss", { id }),
|
|
1138
|
+
validateAutomationAsset: (id) => this.mutate("validate", { id }),
|
|
1139
|
+
testAutomationAsset: (id, url, inputs) => this.mutate("test", {
|
|
1140
|
+
id,
|
|
1141
|
+
url,
|
|
1142
|
+
inputs
|
|
1143
|
+
}),
|
|
1144
|
+
setAutomationAssetStatus: (id, status) => this.mutate("status", {
|
|
1145
|
+
id,
|
|
1146
|
+
status
|
|
1147
|
+
})
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
snapshot() {
|
|
1151
|
+
return this.store.getSnapshot();
|
|
1152
|
+
}
|
|
1153
|
+
dispose() {
|
|
1154
|
+
this.disposed = true;
|
|
1155
|
+
}
|
|
1156
|
+
async refresh() {
|
|
1157
|
+
this.publish({
|
|
1158
|
+
...this.store.getSnapshot(),
|
|
1159
|
+
loading: true,
|
|
1160
|
+
failed: false,
|
|
1161
|
+
error: void 0
|
|
1162
|
+
});
|
|
1163
|
+
try {
|
|
1164
|
+
const snapshot = await this.call("snapshot", {});
|
|
1165
|
+
this.publish({
|
|
1166
|
+
...this.store.getSnapshot(),
|
|
1167
|
+
loading: false,
|
|
1168
|
+
snapshot
|
|
1169
|
+
});
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
this.fail(error);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async select(id) {
|
|
1175
|
+
if (!id) {
|
|
1176
|
+
this.publish({
|
|
1177
|
+
...this.store.getSnapshot(),
|
|
1178
|
+
selected: void 0
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
const selected = await this.call("get", { id });
|
|
1184
|
+
this.publish({
|
|
1185
|
+
...this.store.getSnapshot(),
|
|
1186
|
+
selected: selected ?? void 0,
|
|
1187
|
+
failed: false,
|
|
1188
|
+
error: void 0
|
|
1189
|
+
});
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
this.fail(error);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
async mutate(endpoint, payload) {
|
|
1195
|
+
this.publish({
|
|
1196
|
+
...this.store.getSnapshot(),
|
|
1197
|
+
busy: true,
|
|
1198
|
+
failed: false,
|
|
1199
|
+
error: void 0
|
|
1200
|
+
});
|
|
1201
|
+
try {
|
|
1202
|
+
const value = await this.call(endpoint, payload);
|
|
1203
|
+
const selected = value && typeof value === "object" && "id" in value ? value : this.store.getSnapshot().selected;
|
|
1204
|
+
const snapshot = await this.call("snapshot", {});
|
|
1205
|
+
this.publish({
|
|
1206
|
+
loading: false,
|
|
1207
|
+
busy: false,
|
|
1208
|
+
failed: false,
|
|
1209
|
+
snapshot,
|
|
1210
|
+
...selected ? { selected } : {}
|
|
1211
|
+
});
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
this.fail(error);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async call(endpoint, payload) {
|
|
1217
|
+
const result = await this.rpc.call("/dsh-browser-assets", endpoint, payload);
|
|
1218
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
1219
|
+
return result.value;
|
|
1220
|
+
}
|
|
1221
|
+
fail(error) {
|
|
1222
|
+
this.publish({
|
|
1223
|
+
...this.store.getSnapshot(),
|
|
1224
|
+
loading: false,
|
|
1225
|
+
busy: false,
|
|
1226
|
+
failed: true,
|
|
1227
|
+
error: String(error instanceof Error ? error.message : error).slice(0, 300)
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
publish(state) {
|
|
1231
|
+
if (!this.disposed) this.store.set(state);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/client/index.ts
|
|
1236
|
+
const name = "dsh-browser-client";
|
|
1237
|
+
const inject = [
|
|
1238
|
+
"slots",
|
|
1239
|
+
"locale",
|
|
1240
|
+
"connection",
|
|
1241
|
+
"settingsScope"
|
|
1242
|
+
];
|
|
1243
|
+
const NS = "dsh-browser.card";
|
|
1244
|
+
function apply(ctx) {
|
|
1245
|
+
ensureStyles();
|
|
1246
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1247
|
+
zh,
|
|
1248
|
+
en
|
|
1249
|
+
}), "dsh-browser: settings dictionaries");
|
|
1250
|
+
const controller = new BrowserSettingsController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }));
|
|
1251
|
+
const assets = new AutomationAssetsController(ctx.connection.rpc);
|
|
1252
|
+
ctx.effect(() => () => controller.dispose(), "dsh-browser: settings controller");
|
|
1253
|
+
ctx.effect(() => () => assets.dispose(), "dsh-browser: automation assets controller");
|
|
1254
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1255
|
+
name: "settings.plugin.item",
|
|
1256
|
+
key: SETTINGS_NAMESPACE,
|
|
1257
|
+
locale: NS,
|
|
1258
|
+
inject: () => {
|
|
1259
|
+
const settingsProps = controller.inject();
|
|
1260
|
+
const assetProps = assets.inject();
|
|
1261
|
+
return {
|
|
1262
|
+
...settingsProps,
|
|
1263
|
+
...assetProps,
|
|
1264
|
+
hooks: {
|
|
1265
|
+
...settingsProps.hooks,
|
|
1266
|
+
...assetProps.hooks
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
}, SettingsCard));
|
|
1271
|
+
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
exports.NS = NS;
|
|
1274
|
+
exports.SETTINGS_NAMESPACE = SETTINGS_NAMESPACE;
|
|
1275
|
+
exports.apply = apply;
|
|
1276
|
+
exports.inject = inject;
|
|
1277
|
+
exports.name = name;
|
|
1278
|
+
return module.exports;
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
//# sourceMappingURL=client.js.map
|