@klarkxy/dsh-safe-auto 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -7
- package/README.zh-CN.md +24 -7
- package/dsh.plugin.json +3 -3
- package/lib/client.js +471 -61
- package/package.json +19 -11
- package/src/audit.js +706 -0
- package/src/index.js +229 -16
- package/src/model-route.js +0 -2
- package/src/reviewer.js +27 -14
- package/src/ui-host.js +9 -0
package/lib/client.js
CHANGED
|
@@ -294,7 +294,14 @@ function useModelCatalog(load, options = {}) {
|
|
|
294
294
|
refresh
|
|
295
295
|
};
|
|
296
296
|
}
|
|
297
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* Also accepts a mapped state from a caller that owns broader async orchestration.
|
|
299
|
+
*
|
|
300
|
+
* The dsh-ui-* classes carry the styling and resolve through the consuming
|
|
301
|
+
* plugin's own officialUiCss scope (every consumer mounts its root class, so
|
|
302
|
+
* e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
|
|
303
|
+
* stays as a structural hook for tests and layout, never a colour source.
|
|
304
|
+
*/
|
|
298
305
|
function ModelCatalogStatus({ state, locale }) {
|
|
299
306
|
const zh = locale?.toLowerCase().startsWith("zh") === true;
|
|
300
307
|
const copy = zh ? {
|
|
@@ -310,15 +317,17 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
310
317
|
};
|
|
311
318
|
if (!state.loading && !state.error && state.failures.length === 0) return null;
|
|
312
319
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
313
|
-
className: "dsh-model-catalog-status",
|
|
320
|
+
className: "dsh-model-catalog-status dsh-ui-stack",
|
|
314
321
|
lang: zh ? "zh" : "en",
|
|
315
322
|
children: [
|
|
316
323
|
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
317
324
|
role: "status",
|
|
325
|
+
className: "dsh-ui-loading",
|
|
318
326
|
children: copy.loading
|
|
319
327
|
}) : null,
|
|
320
328
|
state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
321
329
|
role: "alert",
|
|
330
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
322
331
|
children: [
|
|
323
332
|
copy.error,
|
|
324
333
|
": ",
|
|
@@ -327,20 +336,27 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
327
336
|
}) : null,
|
|
328
337
|
state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
329
338
|
role: "status",
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
failure.
|
|
334
|
-
|
|
339
|
+
className: "dsh-ui-meta",
|
|
340
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
341
|
+
className: "dsh-ui-list",
|
|
342
|
+
children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
343
|
+
failure.name || failure.id,
|
|
344
|
+
": ",
|
|
345
|
+
failure.message
|
|
346
|
+
] }, `${failure.id}:${index}`))
|
|
347
|
+
})]
|
|
335
348
|
}) : null,
|
|
336
|
-
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
state.
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
350
|
+
className: "dsh-ui-actions",
|
|
351
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
352
|
+
type: "button",
|
|
353
|
+
size: "sm",
|
|
354
|
+
disabled: state.loading,
|
|
355
|
+
onClick: () => {
|
|
356
|
+
state.refresh();
|
|
357
|
+
},
|
|
358
|
+
children: copy.retry
|
|
359
|
+
})
|
|
344
360
|
}) : null
|
|
345
361
|
]
|
|
346
362
|
});
|
|
@@ -358,7 +374,8 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
358
374
|
* Sticky group headings, subsequence ranking, highlight walking and focus
|
|
359
375
|
* hand-back all match the host. SYNC POINT: when the host's ModelSelect or
|
|
360
376
|
* ModelSelect.module.css changes, this copy must be re-transcribed; styles
|
|
361
|
-
* below may only reference
|
|
377
|
+
* below may only reference allowlisted theme tokens (the --dsw-* set plus the
|
|
378
|
+
* host's --ds-* motion tokens), never literal colors.
|
|
362
379
|
*
|
|
363
380
|
* It statically imports the primitives, so only browser clients should load
|
|
364
381
|
* this entry; host code and node tests use the core entry (`./index.ts`).
|
|
@@ -866,7 +883,8 @@ function ModelMenu(props) {
|
|
|
866
883
|
* sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
|
|
867
884
|
* elevation at portal z-index 1100. The trigger mirrors the composer's 28px
|
|
868
885
|
* model pill, chevron rotating open. MenuSurface paints fill and backdrop.
|
|
869
|
-
* Only --dsw
|
|
886
|
+
* Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
|
|
887
|
+
* color is a bug.
|
|
870
888
|
*/
|
|
871
889
|
const modelMenuCss = `
|
|
872
890
|
.dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
|
|
@@ -889,12 +907,14 @@ const modelMenuCss = `
|
|
|
889
907
|
display: flex;
|
|
890
908
|
}
|
|
891
909
|
.dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
892
|
-
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0
|
|
910
|
+
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 var(--dsw-focus-ring-width) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
|
|
893
911
|
.dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
894
912
|
.dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
|
|
895
913
|
.dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
|
|
896
|
-
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform
|
|
914
|
+
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
|
|
897
915
|
.dsh-model-menu-chevronOpen { transform: rotate(180deg); }
|
|
916
|
+
/* Portal overlay: the host publishes no z-index token, so the card keeps the
|
|
917
|
+
* native menu layer's 1100 verbatim. */
|
|
898
918
|
.dsh-model-menu-menu {
|
|
899
919
|
z-index: 1100;
|
|
900
920
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
@@ -920,6 +940,8 @@ const modelMenuCss = `
|
|
|
920
940
|
.dsh-model-menu-searchWithQuery { padding-right: 34px; }
|
|
921
941
|
.dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
|
|
922
942
|
.dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
|
|
943
|
+
/* The clear button is a circle by geometry (border-radius: 50%), a shape the
|
|
944
|
+
* radius token scale does not describe. */
|
|
923
945
|
.dsh-model-menu-searchClear {
|
|
924
946
|
width: 24px;
|
|
925
947
|
height: 24px;
|
|
@@ -1167,6 +1189,17 @@ ${scope} {
|
|
|
1167
1189
|
/* --- Layout scaffolding. --- */
|
|
1168
1190
|
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
1169
1191
|
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
1192
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
1193
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
1194
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
1195
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
1196
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
1197
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
1198
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
1199
|
+
& .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
1200
|
+
@media (max-width: 560px) {
|
|
1201
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
1202
|
+
}
|
|
1170
1203
|
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1171
1204
|
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
1172
1205
|
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
@@ -1194,6 +1227,9 @@ ${scope} {
|
|
|
1194
1227
|
* official primitive, so this only owns the vertical rhythm. --- */
|
|
1195
1228
|
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
1196
1229
|
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
1230
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
1231
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
1232
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
1197
1233
|
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
1198
1234
|
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
1199
1235
|
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
@@ -1384,7 +1420,7 @@ ${scope} {
|
|
|
1384
1420
|
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
1385
1421
|
}
|
|
1386
1422
|
}
|
|
1387
|
-
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(
|
|
1423
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
1388
1424
|
}
|
|
1389
1425
|
//#endregion
|
|
1390
1426
|
//#region ../dsh-plugin-kit/lib/client-utils.js
|
|
@@ -1414,25 +1450,18 @@ function createPluginReadGate(now = Date.now) {
|
|
|
1414
1450
|
}
|
|
1415
1451
|
//#endregion
|
|
1416
1452
|
//#region ../dsh-plugin-kit/lib/contracts.js
|
|
1417
|
-
/**
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
method: "watch",
|
|
1430
|
-
mode: "stream",
|
|
1431
|
-
invocation: { kind: "direct" },
|
|
1432
|
-
parameters: [],
|
|
1433
|
-
cancellation: { parameter: "signal" }
|
|
1434
|
-
}]
|
|
1435
|
-
};
|
|
1453
|
+
/** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
|
|
1454
|
+
function modelSettingsChangesIdentity(packageName) {
|
|
1455
|
+
const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
|
|
1456
|
+
const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
1457
|
+
if (!parts.length) throw new Error("Model settings package name has no RPC segment");
|
|
1458
|
+
const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
|
|
1459
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
|
|
1460
|
+
return {
|
|
1461
|
+
service: segment,
|
|
1462
|
+
namespace: segment
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1436
1465
|
//#endregion
|
|
1437
1466
|
//#region ../dsh-plugin-kit/lib/model-settings-client.js
|
|
1438
1467
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
@@ -1447,17 +1476,44 @@ function parseModelSettingsChangeFrame(value) {
|
|
|
1447
1476
|
};
|
|
1448
1477
|
throw new Error("Invalid model settings change frame");
|
|
1449
1478
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1479
|
+
/** One owner's contribution. Each package mounts only its own endpoint. */
|
|
1480
|
+
function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
|
|
1481
|
+
return {
|
|
1482
|
+
package: packageName,
|
|
1483
|
+
descriptors: [{
|
|
1484
|
+
id: `${identity.namespace}/watch`,
|
|
1485
|
+
service: identity.service,
|
|
1486
|
+
namespace: identity.namespace,
|
|
1487
|
+
method: "watch",
|
|
1488
|
+
mode: "stream",
|
|
1489
|
+
invocation: { kind: "direct" },
|
|
1490
|
+
parameters: [],
|
|
1491
|
+
cancellation: { parameter: "signal" },
|
|
1492
|
+
result: {
|
|
1493
|
+
mode: "strict",
|
|
1494
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
1495
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
1496
|
+
}
|
|
1497
|
+
}]
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
/** Mount from a synchronous apply. Disposal waits for an in-flight mount and
|
|
1501
|
+
* then withdraws it, so the contribution cannot outlive the plugin. */
|
|
1502
|
+
function mountModelSettingsChanges(ctx, packageName) {
|
|
1503
|
+
const contribution = modelSettingsChangesRemote(packageName);
|
|
1504
|
+
ctx.inject(["remote"], (injected) => {
|
|
1505
|
+
const scope = injected;
|
|
1506
|
+
const mounting = scope.remote.$mount(contribution);
|
|
1507
|
+
mounting.catch(() => {});
|
|
1508
|
+
scope.effect(() => async () => {
|
|
1509
|
+
await (await mounting.catch(() => void 0))?.();
|
|
1510
|
+
});
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1458
1513
|
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
1459
1514
|
* A factory shares one logical stream while at least one subscriber remains. */
|
|
1460
|
-
function createModelSettingsSubscriber(ctx,
|
|
1515
|
+
function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
|
|
1516
|
+
const endpoint = identity;
|
|
1461
1517
|
const listeners = /* @__PURE__ */ new Set();
|
|
1462
1518
|
let injection;
|
|
1463
1519
|
let current;
|
|
@@ -1483,12 +1539,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
1483
1539
|
};
|
|
1484
1540
|
const start = () => {
|
|
1485
1541
|
const life = ++lifecycle;
|
|
1486
|
-
injection = ctx.inject(["remote",
|
|
1542
|
+
injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
|
|
1487
1543
|
if (life !== lifecycle || !listeners.size) return;
|
|
1488
1544
|
const scope = injected;
|
|
1545
|
+
const service = scope.remote[endpoint.namespace];
|
|
1546
|
+
if (!service || !("watch" in service) || !service.watch) {
|
|
1547
|
+
report("Model settings sync service unavailable");
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const watch = service.watch;
|
|
1489
1551
|
const stream = scope.remote.$stream({
|
|
1490
1552
|
name: "Model settings sync",
|
|
1491
|
-
open: (signal) =>
|
|
1553
|
+
open: (signal) => watch(signal),
|
|
1492
1554
|
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
1493
1555
|
});
|
|
1494
1556
|
const session = {
|
|
@@ -1523,7 +1585,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
1523
1585
|
invalidate();
|
|
1524
1586
|
} else {
|
|
1525
1587
|
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
1526
|
-
if (
|
|
1588
|
+
if (accept(frame.ns)) invalidate();
|
|
1527
1589
|
}
|
|
1528
1590
|
}
|
|
1529
1591
|
} catch (error) {
|
|
@@ -1844,9 +1906,6 @@ const css = `${officialUiCss("dsh-safe-auto")}${modelMenuCss}
|
|
|
1844
1906
|
* legend keeps the UA inline padding from indenting the group title. */
|
|
1845
1907
|
.dsh-safe-auto legend { padding: 0; }
|
|
1846
1908
|
`;
|
|
1847
|
-
function Style() {
|
|
1848
|
-
return h("style", null, css);
|
|
1849
|
-
}
|
|
1850
1909
|
function ErrorText({ error, t }) {
|
|
1851
1910
|
return error ? h("p", {
|
|
1852
1911
|
role: "alert",
|
|
@@ -1939,7 +1998,7 @@ function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null
|
|
|
1939
1998
|
return h("span", {
|
|
1940
1999
|
className: "dsh-safe-auto dsh-safe-auto-permission",
|
|
1941
2000
|
"aria-busy": busy
|
|
1942
|
-
}, h(
|
|
2001
|
+
}, h(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1943
2002
|
open,
|
|
1944
2003
|
side: "top",
|
|
1945
2004
|
align: "start",
|
|
@@ -1978,7 +2037,7 @@ function ReviewerFields({ values, catalog, disabled, onChange, locale }) {
|
|
|
1978
2037
|
const retained = effort && h("span", null, " ", t("savedEffortRetained", effort));
|
|
1979
2038
|
return h("fieldset", {
|
|
1980
2039
|
disabled,
|
|
1981
|
-
className: "dsh-ui-card"
|
|
2040
|
+
className: "dsh-safe-auto dsh-ui-card"
|
|
1982
2041
|
}, h("legend", { className: "dsh-ui-heading" }, t("reviewer")), h("div", { className: "dsh-ui-field" }, h("span", { className: "dsh-ui-label" }, t("model")), h(ModelMenu, {
|
|
1983
2042
|
open,
|
|
1984
2043
|
onOpenChange: setOpen,
|
|
@@ -2021,6 +2080,339 @@ function ReviewerFields({ values, catalog, disabled, onChange, locale }) {
|
|
|
2021
2080
|
clearSearchLabel: t("clearSearch")
|
|
2022
2081
|
})), state.follow && h("p", { className: "dsh-ui-help" }, t("followHelp"), retained), !state.follow && !state.known && h("p", { className: "dsh-ui-help" }, t("modelAbsent")), !state.follow && unknownEffort && h("p", { className: "dsh-ui-help" }, t("effortAbsent")), !state.follow && h("p", { className: "dsh-ui-help" }, t("effortClears")));
|
|
2023
2082
|
}
|
|
2083
|
+
const HISTORY_TEXT = {
|
|
2084
|
+
zh: {
|
|
2085
|
+
title: "审核历史",
|
|
2086
|
+
help: "保存启用此功能后的审核。内容已尽力脱敏;记录持续保留,可导出供审计。",
|
|
2087
|
+
search: "搜索内容",
|
|
2088
|
+
session: "会话 ID",
|
|
2089
|
+
tool: "工具",
|
|
2090
|
+
outcome: "审核结果",
|
|
2091
|
+
risk: "风险",
|
|
2092
|
+
from: "开始时间",
|
|
2093
|
+
to: "结束时间",
|
|
2094
|
+
all: "全部",
|
|
2095
|
+
apply: "查询",
|
|
2096
|
+
refresh: "刷新",
|
|
2097
|
+
next: "下一页",
|
|
2098
|
+
back: "上一页",
|
|
2099
|
+
detail: "查看详情",
|
|
2100
|
+
empty: "没有匹配的审核记录",
|
|
2101
|
+
loading: "正在查询…",
|
|
2102
|
+
exportOne: "导出此记录",
|
|
2103
|
+
exportAll: "导出筛选结果",
|
|
2104
|
+
cancel: "取消导出",
|
|
2105
|
+
exporting: (count) => `正在导出 ${count} 条…`,
|
|
2106
|
+
count: (count) => `共 ${count} 条`,
|
|
2107
|
+
missing: "审核记录不存在",
|
|
2108
|
+
execution: "工具结果",
|
|
2109
|
+
approval: "实际审批",
|
|
2110
|
+
approvalUnknown: "未观察到审批结果",
|
|
2111
|
+
unavailable: "未记录",
|
|
2112
|
+
outcomes: {
|
|
2113
|
+
pending: "审核中",
|
|
2114
|
+
interrupted: "审核中断",
|
|
2115
|
+
"allowed-once": "审核通过",
|
|
2116
|
+
rejected: "拒绝",
|
|
2117
|
+
cancelled: "取消",
|
|
2118
|
+
unavailable: "审批不可用"
|
|
2119
|
+
},
|
|
2120
|
+
risks: {
|
|
2121
|
+
low: "低",
|
|
2122
|
+
medium: "中",
|
|
2123
|
+
high: "高",
|
|
2124
|
+
critical: "严重"
|
|
2125
|
+
},
|
|
2126
|
+
states: {
|
|
2127
|
+
"not-observed": "未观察到执行结果",
|
|
2128
|
+
succeeded: "工具返回成功",
|
|
2129
|
+
failed: "工具返回错误"
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
2132
|
+
en: {
|
|
2133
|
+
title: "Review history",
|
|
2134
|
+
help: "Reviews are retained from the time this feature is enabled. Content is redacted on a best-effort basis and can be exported for audit.",
|
|
2135
|
+
search: "Search contents",
|
|
2136
|
+
session: "Session ID",
|
|
2137
|
+
tool: "Tool",
|
|
2138
|
+
outcome: "Outcome",
|
|
2139
|
+
risk: "Risk",
|
|
2140
|
+
from: "From",
|
|
2141
|
+
to: "To",
|
|
2142
|
+
all: "All",
|
|
2143
|
+
apply: "Search",
|
|
2144
|
+
refresh: "Refresh",
|
|
2145
|
+
next: "Next",
|
|
2146
|
+
back: "Previous",
|
|
2147
|
+
detail: "View details",
|
|
2148
|
+
empty: "No matching reviews",
|
|
2149
|
+
loading: "Loading reviews…",
|
|
2150
|
+
exportOne: "Export this record",
|
|
2151
|
+
exportAll: "Export filtered results",
|
|
2152
|
+
cancel: "Cancel export",
|
|
2153
|
+
exporting: (count) => `Exporting ${count} records…`,
|
|
2154
|
+
count: (count) => `${count} records`,
|
|
2155
|
+
missing: "Review record not found",
|
|
2156
|
+
execution: "Tool result",
|
|
2157
|
+
approval: "Actual approval",
|
|
2158
|
+
approvalUnknown: "No approval result observed",
|
|
2159
|
+
unavailable: "Not recorded",
|
|
2160
|
+
outcomes: {
|
|
2161
|
+
pending: "Pending",
|
|
2162
|
+
interrupted: "Interrupted",
|
|
2163
|
+
"allowed-once": "Review passed",
|
|
2164
|
+
rejected: "Rejected",
|
|
2165
|
+
cancelled: "Cancelled",
|
|
2166
|
+
unavailable: "Approval unavailable"
|
|
2167
|
+
},
|
|
2168
|
+
risks: {
|
|
2169
|
+
low: "Low",
|
|
2170
|
+
medium: "Medium",
|
|
2171
|
+
high: "High",
|
|
2172
|
+
critical: "Critical"
|
|
2173
|
+
},
|
|
2174
|
+
states: {
|
|
2175
|
+
"not-observed": "No execution result observed",
|
|
2176
|
+
succeeded: "Tool returned success",
|
|
2177
|
+
failed: "Tool returned an error"
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
function historyFilters(values) {
|
|
2182
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== "").map(([key, value]) => [key, key === "from" || key === "to" ? new Date(value).toISOString() : value]));
|
|
2183
|
+
}
|
|
2184
|
+
function downloadHistory(records, filename = "safe-auto-history.json") {
|
|
2185
|
+
const url = URL.createObjectURL(new Blob([JSON.stringify({
|
|
2186
|
+
format: "safe-auto-audit-v1",
|
|
2187
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2188
|
+
count: records.length,
|
|
2189
|
+
records
|
|
2190
|
+
}, null, 2)], { type: "application/json" }));
|
|
2191
|
+
const link = document.createElement("a");
|
|
2192
|
+
link.href = url;
|
|
2193
|
+
link.download = filename;
|
|
2194
|
+
link.click();
|
|
2195
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
2196
|
+
}
|
|
2197
|
+
function HistoryPanel({ api, locale, initialPage = null, download = downloadHistory }) {
|
|
2198
|
+
const language = (0, react.useSyncExternalStore)((listener) => locale?.subscribe?.(listener) ?? (() => {}), () => localeLanguage(locale), () => localeLanguage(locale));
|
|
2199
|
+
const t = HISTORY_TEXT[language];
|
|
2200
|
+
const [draft, setDraft] = (0, react.useState)({
|
|
2201
|
+
query: "",
|
|
2202
|
+
sessionId: "",
|
|
2203
|
+
tool: "",
|
|
2204
|
+
outcome: "",
|
|
2205
|
+
risk: "",
|
|
2206
|
+
from: "",
|
|
2207
|
+
to: ""
|
|
2208
|
+
});
|
|
2209
|
+
const [filter, setFilter] = (0, react.useState)({});
|
|
2210
|
+
const [page, setPage] = (0, react.useState)(initialPage);
|
|
2211
|
+
const [cursors, setCursors] = (0, react.useState)([void 0]);
|
|
2212
|
+
const [index, setIndex] = (0, react.useState)(0);
|
|
2213
|
+
const [error, setError] = (0, react.useState)("");
|
|
2214
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
2215
|
+
const [detail, setDetail] = (0, react.useState)(null);
|
|
2216
|
+
const [exporting, setExporting] = (0, react.useState)(null);
|
|
2217
|
+
const generation = (0, react.useRef)(0), detailsGeneration = (0, react.useRef)(0), exportGeneration = (0, react.useRef)(0);
|
|
2218
|
+
const alive = (0, react.useRef)(true), filterRef = (0, react.useRef)(filter);
|
|
2219
|
+
filterRef.current = filter;
|
|
2220
|
+
const load = (0, react.useCallback)(async (filters, cursor) => {
|
|
2221
|
+
const ticket = ++generation.current;
|
|
2222
|
+
++detailsGeneration.current;
|
|
2223
|
+
++exportGeneration.current;
|
|
2224
|
+
setBusy(true);
|
|
2225
|
+
setError("");
|
|
2226
|
+
setDetail(null);
|
|
2227
|
+
setExporting(null);
|
|
2228
|
+
try {
|
|
2229
|
+
const result = await api.historyList({
|
|
2230
|
+
...filters,
|
|
2231
|
+
limit: 25,
|
|
2232
|
+
...cursor ? { cursor } : {}
|
|
2233
|
+
});
|
|
2234
|
+
if (alive.current && ticket === generation.current) setPage(result);
|
|
2235
|
+
} catch (e) {
|
|
2236
|
+
if (alive.current && ticket === generation.current) {
|
|
2237
|
+
setError(errorMessage(e));
|
|
2238
|
+
setPage(null);
|
|
2239
|
+
}
|
|
2240
|
+
} finally {
|
|
2241
|
+
if (alive.current && ticket === generation.current) setBusy(false);
|
|
2242
|
+
}
|
|
2243
|
+
}, [api]);
|
|
2244
|
+
(0, react.useEffect)(() => {
|
|
2245
|
+
alive.current = true;
|
|
2246
|
+
load({}, void 0);
|
|
2247
|
+
const stop = api.subscribeGeneration?.(() => {
|
|
2248
|
+
setCursors([void 0]);
|
|
2249
|
+
setIndex(0);
|
|
2250
|
+
load(filterRef.current, void 0);
|
|
2251
|
+
});
|
|
2252
|
+
return () => {
|
|
2253
|
+
alive.current = false;
|
|
2254
|
+
generation.current++;
|
|
2255
|
+
detailsGeneration.current++;
|
|
2256
|
+
exportGeneration.current++;
|
|
2257
|
+
stop?.();
|
|
2258
|
+
};
|
|
2259
|
+
}, [load, api]);
|
|
2260
|
+
const search = (e) => {
|
|
2261
|
+
e?.preventDefault();
|
|
2262
|
+
try {
|
|
2263
|
+
const next = historyFilters(draft);
|
|
2264
|
+
setFilter(next);
|
|
2265
|
+
setCursors([void 0]);
|
|
2266
|
+
setIndex(0);
|
|
2267
|
+
load(next);
|
|
2268
|
+
} catch (e) {
|
|
2269
|
+
setError(errorMessage(e));
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
const showDetail = async (id) => {
|
|
2273
|
+
const ticket = ++detailsGeneration.current;
|
|
2274
|
+
setError("");
|
|
2275
|
+
setDetail(null);
|
|
2276
|
+
try {
|
|
2277
|
+
const row = await api.historyGet(id);
|
|
2278
|
+
if (!row) throw new Error(t.missing);
|
|
2279
|
+
if (alive.current && ticket === detailsGeneration.current) setDetail(row);
|
|
2280
|
+
} catch (e) {
|
|
2281
|
+
if (alive.current && ticket === detailsGeneration.current) setError(errorMessage(e));
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
const exportAll = async () => {
|
|
2285
|
+
const ticket = ++exportGeneration.current;
|
|
2286
|
+
const records = [];
|
|
2287
|
+
let cursor;
|
|
2288
|
+
setExporting(0);
|
|
2289
|
+
setError("");
|
|
2290
|
+
try {
|
|
2291
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2292
|
+
const filters = {
|
|
2293
|
+
...filter,
|
|
2294
|
+
to: filter.to && Date.parse(filter.to) < Date.parse(now) ? filter.to : now
|
|
2295
|
+
};
|
|
2296
|
+
do {
|
|
2297
|
+
const batch = await api.historyList({
|
|
2298
|
+
...filters,
|
|
2299
|
+
limit: 100,
|
|
2300
|
+
...cursor ? { cursor } : {}
|
|
2301
|
+
});
|
|
2302
|
+
if (!alive.current || ticket !== exportGeneration.current) return;
|
|
2303
|
+
for (const item of batch.items) {
|
|
2304
|
+
const row = await api.historyGet(item.id);
|
|
2305
|
+
if (!alive.current || ticket !== exportGeneration.current) return;
|
|
2306
|
+
if (!row) throw new Error(t.missing);
|
|
2307
|
+
records.push(row);
|
|
2308
|
+
setExporting(records.length);
|
|
2309
|
+
}
|
|
2310
|
+
if (batch.nextCursor && batch.nextCursor === cursor) throw new Error("Invalid history cursor");
|
|
2311
|
+
cursor = batch.nextCursor;
|
|
2312
|
+
} while (cursor);
|
|
2313
|
+
download(records);
|
|
2314
|
+
} catch (e) {
|
|
2315
|
+
if (alive.current && ticket === exportGeneration.current) setError(errorMessage(e));
|
|
2316
|
+
} finally {
|
|
2317
|
+
if (alive.current && ticket === exportGeneration.current) setExporting(null);
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
const field = (key, label, options) => h("label", {
|
|
2321
|
+
key,
|
|
2322
|
+
className: "dsh-ui-field"
|
|
2323
|
+
}, h("span", { className: "dsh-ui-label" }, label), options ? h("select", {
|
|
2324
|
+
className: "dsh-ui-select",
|
|
2325
|
+
value: draft[key],
|
|
2326
|
+
onChange: (e) => setDraft({
|
|
2327
|
+
...draft,
|
|
2328
|
+
[key]: e.target.value
|
|
2329
|
+
})
|
|
2330
|
+
}, h("option", { value: "" }, t.all), ...Object.entries(options).map(([value, label]) => h("option", {
|
|
2331
|
+
key: value,
|
|
2332
|
+
value
|
|
2333
|
+
}, label))) : h(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
2334
|
+
type: key === "from" || key === "to" ? "datetime-local" : "text",
|
|
2335
|
+
maxLength: 256,
|
|
2336
|
+
value: draft[key],
|
|
2337
|
+
onChange: (e) => setDraft({
|
|
2338
|
+
...draft,
|
|
2339
|
+
[key]: e.target.value
|
|
2340
|
+
})
|
|
2341
|
+
}));
|
|
2342
|
+
return h("section", {
|
|
2343
|
+
className: "dsh-ui-stack",
|
|
2344
|
+
"aria-label": t.title,
|
|
2345
|
+
"aria-busy": busy
|
|
2346
|
+
}, h("p", { className: "dsh-ui-help" }, t.help), h("form", {
|
|
2347
|
+
className: "dsh-ui-stack",
|
|
2348
|
+
onSubmit: search
|
|
2349
|
+
}, h("div", { className: "dsh-ui-fields-grid" }, field("query", t.search), field("sessionId", t.session), field("tool", t.tool), field("outcome", t.outcome, t.outcomes), field("risk", t.risk, t.risks), field("from", t.from), field("to", t.to)), h("div", { className: "dsh-ui-actions" }, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2350
|
+
type: "submit",
|
|
2351
|
+
variant: "primary"
|
|
2352
|
+
}, t.apply), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2353
|
+
type: "button",
|
|
2354
|
+
variant: "ghost",
|
|
2355
|
+
onClick: () => {
|
|
2356
|
+
setCursors([void 0]);
|
|
2357
|
+
setIndex(0);
|
|
2358
|
+
load(filter);
|
|
2359
|
+
}
|
|
2360
|
+
}, t.refresh), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2361
|
+
type: "button",
|
|
2362
|
+
variant: "ghost",
|
|
2363
|
+
disabled: busy || exporting !== null || !page?.total,
|
|
2364
|
+
onClick: exportAll
|
|
2365
|
+
}, t.exportAll))), error && h("p", {
|
|
2366
|
+
role: "alert",
|
|
2367
|
+
className: "dsh-ui-error"
|
|
2368
|
+
}, error), exporting !== null && h("div", {
|
|
2369
|
+
className: "dsh-ui-actions",
|
|
2370
|
+
role: "status"
|
|
2371
|
+
}, t.exporting(exporting), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2372
|
+
variant: "ghost",
|
|
2373
|
+
onClick: () => {
|
|
2374
|
+
exportGeneration.current++;
|
|
2375
|
+
setExporting(null);
|
|
2376
|
+
}
|
|
2377
|
+
}, t.cancel)), busy ? h("p", { role: "status" }, t.loading) : page && h(react.default.Fragment, null, h("p", { className: "dsh-ui-meta" }, t.count(page.total)), !page.items.length && h("p", { className: "dsh-ui-empty" }, t.empty), ...page.items.map((row) => h("article", {
|
|
2378
|
+
key: row.id,
|
|
2379
|
+
className: "dsh-ui-card dsh-ui-stack"
|
|
2380
|
+
}, h("div", { className: "dsh-ui-row-wrap" }, h("strong", null, row.tool), h("span", null, t.outcomes[row.reviewOutcome || row.outcome] || row.reviewOutcome || row.outcome), h("span", null, t.risks[row.risk] || row.risk || "")), h("p", { className: "dsh-ui-meta dsh-ui-wrap" }, new Date(row.startedAt).toLocaleString(language), " · ", row.sessionId, " · ", row.reviewer ? `${row.reviewer.provider}/${row.reviewer.model}` : t.unavailable), row.rationale && h("p", { className: "dsh-ui-wrap" }, row.rationale), h("p", { className: "dsh-ui-help" }, t.approval, ": ", !row.approvalState || row.approvalState === "not-observed" ? t.approvalUnknown : row.approvalState === "allowed-once" ? language === "zh" ? "单次放行" : "Allowed once" : t.outcomes[row.approvalState]), h("p", { className: "dsh-ui-help" }, t.execution, ": ", t.states[row.executionState || "not-observed"]), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2381
|
+
variant: "ghost",
|
|
2382
|
+
onClick: () => showDetail(row.id)
|
|
2383
|
+
}, t.detail))), h("div", { className: "dsh-ui-actions" }, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2384
|
+
variant: "ghost",
|
|
2385
|
+
disabled: index === 0,
|
|
2386
|
+
onClick: () => {
|
|
2387
|
+
const next = index - 1;
|
|
2388
|
+
setIndex(next);
|
|
2389
|
+
load(filter, cursors[next]);
|
|
2390
|
+
}
|
|
2391
|
+
}, t.back), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2392
|
+
variant: "ghost",
|
|
2393
|
+
disabled: !page.nextCursor,
|
|
2394
|
+
onClick: () => {
|
|
2395
|
+
const next = index + 1;
|
|
2396
|
+
setCursors([...cursors.slice(0, next), page.nextCursor]);
|
|
2397
|
+
setIndex(next);
|
|
2398
|
+
load(filter, page.nextCursor);
|
|
2399
|
+
}
|
|
2400
|
+
}, t.next))), detail && h("section", {
|
|
2401
|
+
className: "dsh-ui-stack",
|
|
2402
|
+
"aria-label": t.detail
|
|
2403
|
+
}, h("h3", null, detail.id), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2404
|
+
variant: "ghost",
|
|
2405
|
+
onClick: () => download([detail], `safe-auto-${detail.id}.json`)
|
|
2406
|
+
}, t.exportOne), h("pre", {
|
|
2407
|
+
className: "dsh-ui-wrap",
|
|
2408
|
+
style: {
|
|
2409
|
+
whiteSpace: "pre-wrap",
|
|
2410
|
+
overflowWrap: "anywhere",
|
|
2411
|
+
maxHeight: "60vh",
|
|
2412
|
+
overflow: "auto"
|
|
2413
|
+
}
|
|
2414
|
+
}, JSON.stringify(detail, null, 2))));
|
|
2415
|
+
}
|
|
2024
2416
|
function SettingsPanel({ view, api, locale, initialSettings = null, initialCatalog = null, initialTab = "reviewer" }) {
|
|
2025
2417
|
const t = useText(locale);
|
|
2026
2418
|
const [settings, setSettings] = (0, react.useState)(initialSettings);
|
|
@@ -2171,21 +2563,22 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
|
|
|
2171
2563
|
tabIndex: 0,
|
|
2172
2564
|
className
|
|
2173
2565
|
}, children);
|
|
2174
|
-
if (view === "summary") return h("div", { className: "dsh-safe-auto" }, h(
|
|
2566
|
+
if (view === "summary") return h("div", { className: "dsh-safe-auto" }, h("p", { className: "dsh-ui-compact dsh-ui-wrap" }, t("summary")), h(ErrorText, {
|
|
2175
2567
|
error,
|
|
2176
2568
|
t
|
|
2177
2569
|
}));
|
|
2178
2570
|
return h("section", {
|
|
2179
|
-
className: "dsh-safe-auto dsh-ui-
|
|
2571
|
+
className: "dsh-safe-auto dsh-ui-page",
|
|
2180
2572
|
"aria-label": t("panelLabel"),
|
|
2181
2573
|
"aria-busy": busy
|
|
2182
|
-
}, h(
|
|
2574
|
+
}, h("p", { className: "dsh-ui-compact dsh-ui-wrap" }, t("intro")), !values && h("p", { className: "dsh-ui-loading" }, t("loading")), values && h(react.default.Fragment, null, h(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
|
|
2183
2575
|
value: activeTab,
|
|
2184
2576
|
onChange: setTab,
|
|
2185
2577
|
label: t("tabsLabel"),
|
|
2186
2578
|
items: [
|
|
2187
2579
|
tabItem("reviewer", t("tabReviewer")),
|
|
2188
2580
|
tabItem("details", t("details")),
|
|
2581
|
+
tabItem("history", HISTORY_TEXT[localeLanguage(locale)].title),
|
|
2189
2582
|
...settings?.policyLimits ? [tabItem("limits", t("tabLimits"))] : []
|
|
2190
2583
|
]
|
|
2191
2584
|
}), tabPanel("reviewer", "dsh-ui-stack", [
|
|
@@ -2217,13 +2610,16 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
|
|
|
2217
2610
|
id: "sa-prompt-hint",
|
|
2218
2611
|
className: "dsh-ui-help"
|
|
2219
2612
|
}, t("promptHint", (values.reviewerPrompt || "").length))
|
|
2220
|
-
]), tabPanel("details", "dsh-ui-stack", h("div", { className: "dsh-ui-help" }, h("p", null, t("safetyDetail")), h("p", null, t("reviewInput")), h("p", null, t("breakerDetail")))),
|
|
2613
|
+
]), tabPanel("details", "dsh-ui-stack", h("div", { className: "dsh-ui-help" }, h("p", null, t("safetyDetail")), h("p", null, t("reviewInput")), h("p", null, t("breakerDetail")))), tabPanel("history", "dsh-ui-stack", activeTab === "history" ? h(HistoryPanel, {
|
|
2614
|
+
api,
|
|
2615
|
+
locale
|
|
2616
|
+
}) : null), settings?.policyLimits && tabPanel("limits", "dsh-ui-stack", [h("p", {
|
|
2221
2617
|
key: "line",
|
|
2222
2618
|
className: "dsh-ui-meta dsh-ui-wrap"
|
|
2223
2619
|
}, t("limitsLine", settings.policyLimits)), h("p", {
|
|
2224
2620
|
key: "help",
|
|
2225
2621
|
className: "dsh-ui-help"
|
|
2226
|
-
}, t("limitsHelp"))]), h("div", { className: "dsh-ui-
|
|
2622
|
+
}, t("limitsHelp"))]), activeTab === "reviewer" && h("div", { className: "dsh-ui-form-footer" }, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2227
2623
|
variant: "primary",
|
|
2228
2624
|
disabled: busy || externalChange || !dirty.current || (values.reviewerPrompt || "").length > 4096,
|
|
2229
2625
|
onClick: save
|
|
@@ -2249,6 +2645,15 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
|
|
|
2249
2645
|
}, t("saved")));
|
|
2250
2646
|
}
|
|
2251
2647
|
function apply(ctx) {
|
|
2648
|
+
ctx.effect(() => {
|
|
2649
|
+
if (typeof document === "undefined") return void 0;
|
|
2650
|
+
const style = document.createElement("style");
|
|
2651
|
+
style.setAttribute("data-plugin", BUNDLE);
|
|
2652
|
+
style.textContent = css;
|
|
2653
|
+
document.head.appendChild(style);
|
|
2654
|
+
return () => style.remove();
|
|
2655
|
+
});
|
|
2656
|
+
mountModelSettingsChanges(ctx, BUNDLE);
|
|
2252
2657
|
const settingsSubscriber = createModelSettingsSubscriber(ctx, BUNDLE);
|
|
2253
2658
|
const call = (endpoint, payload) => unwrapRpc((...args) => ctx.connection.rpc.call(...args), endpoint, payload);
|
|
2254
2659
|
const api = Object.freeze({
|
|
@@ -2257,6 +2662,8 @@ function apply(ctx) {
|
|
|
2257
2662
|
getSettings: () => call("settings.get", {}),
|
|
2258
2663
|
saveSettings: (payload) => call("settings.save", payload),
|
|
2259
2664
|
modelCatalog: () => readModelCatalog(ctx),
|
|
2665
|
+
historyList: (payload) => call("history.list", payload),
|
|
2666
|
+
historyGet: (id) => call("history.get", { id }),
|
|
2260
2667
|
getSession: (sessionId) => call("session.get", { sessionId }),
|
|
2261
2668
|
selectSession: (payload) => call("session.select", payload)
|
|
2262
2669
|
});
|
|
@@ -2280,12 +2687,15 @@ function apply(ctx) {
|
|
|
2280
2687
|
//#endregion
|
|
2281
2688
|
exports.BUNDLE = BUNDLE;
|
|
2282
2689
|
exports.CHANNEL = CHANNEL;
|
|
2690
|
+
exports.HistoryPanel = HistoryPanel;
|
|
2283
2691
|
exports.PermissionMenu = PermissionMenu;
|
|
2284
2692
|
exports.ReviewerFields = ReviewerFields;
|
|
2285
2693
|
exports.SettingsPanel = SettingsPanel;
|
|
2286
2694
|
exports.apply = apply;
|
|
2287
2695
|
exports.createRequestScope = createRequestScope;
|
|
2696
|
+
exports.downloadHistory = downloadHistory;
|
|
2288
2697
|
exports.errorText = errorText;
|
|
2698
|
+
exports.historyFilters = historyFilters;
|
|
2289
2699
|
exports.inject = inject;
|
|
2290
2700
|
exports.localeLanguage = localeLanguage;
|
|
2291
2701
|
exports.name = name;
|