@klarkxy/dsh-git-commit 0.3.0 → 0.3.2
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 +11 -8
- package/docs/README.zh-CN.md +10 -7
- package/lib/client.inner.cjs +819 -112
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +819 -112
- package/lib/contracts.d.ts +1801 -5
- package/lib/contracts.js +2 -35
- package/lib/contracts2.d.ts +2 -0
- package/lib/contracts2.js +85 -0
- package/lib/contracts2.js.map +1 -0
- package/lib/index.d.ts +10 -3
- package/lib/index.js +3935 -22
- package/lib/index.js.map +1 -1
- package/package.json +4 -4
- package/lib/contracts.js.map +0 -1
package/lib/client.js
CHANGED
|
@@ -268,7 +268,14 @@ function useModelCatalog(load, options = {}) {
|
|
|
268
268
|
refresh
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* Also accepts a mapped state from a caller that owns broader async orchestration.
|
|
273
|
+
*
|
|
274
|
+
* The dsh-ui-* classes carry the styling and resolve through the consuming
|
|
275
|
+
* plugin's own officialUiCss scope (every consumer mounts its root class, so
|
|
276
|
+
* e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
|
|
277
|
+
* stays as a structural hook for tests and layout, never a colour source.
|
|
278
|
+
*/
|
|
272
279
|
function ModelCatalogStatus({ state, locale }) {
|
|
273
280
|
const zh = locale?.toLowerCase().startsWith("zh") === true;
|
|
274
281
|
const copy = zh ? {
|
|
@@ -284,15 +291,17 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
284
291
|
};
|
|
285
292
|
if (!state.loading && !state.error && state.failures.length === 0) return null;
|
|
286
293
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
287
|
-
className: "dsh-model-catalog-status",
|
|
294
|
+
className: "dsh-model-catalog-status dsh-ui-stack",
|
|
288
295
|
lang: zh ? "zh" : "en",
|
|
289
296
|
children: [
|
|
290
297
|
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
291
298
|
role: "status",
|
|
299
|
+
className: "dsh-ui-loading",
|
|
292
300
|
children: copy.loading
|
|
293
301
|
}) : null,
|
|
294
302
|
state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
295
303
|
role: "alert",
|
|
304
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
296
305
|
children: [
|
|
297
306
|
copy.error,
|
|
298
307
|
": ",
|
|
@@ -301,20 +310,27 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
301
310
|
}) : null,
|
|
302
311
|
state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
303
312
|
role: "status",
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
failure.
|
|
308
|
-
|
|
313
|
+
className: "dsh-ui-meta",
|
|
314
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
315
|
+
className: "dsh-ui-list",
|
|
316
|
+
children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
317
|
+
failure.name || failure.id,
|
|
318
|
+
": ",
|
|
319
|
+
failure.message
|
|
320
|
+
] }, `${failure.id}:${index}`))
|
|
321
|
+
})]
|
|
309
322
|
}) : null,
|
|
310
|
-
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
state.
|
|
316
|
-
|
|
317
|
-
|
|
323
|
+
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
324
|
+
className: "dsh-ui-actions",
|
|
325
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
326
|
+
type: "button",
|
|
327
|
+
size: "sm",
|
|
328
|
+
disabled: state.loading,
|
|
329
|
+
onClick: () => {
|
|
330
|
+
state.refresh();
|
|
331
|
+
},
|
|
332
|
+
children: copy.retry
|
|
333
|
+
})
|
|
318
334
|
}) : null
|
|
319
335
|
]
|
|
320
336
|
});
|
|
@@ -334,7 +350,8 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
334
350
|
* Sticky group headings, subsequence ranking, highlight walking and focus
|
|
335
351
|
* hand-back all match the host. SYNC POINT: when the host's ModelSelect or
|
|
336
352
|
* ModelSelect.module.css changes, this copy must be re-transcribed; styles
|
|
337
|
-
* below may only reference
|
|
353
|
+
* below may only reference allowlisted theme tokens (the --dsw-* set plus the
|
|
354
|
+
* host's --ds-* motion tokens), never literal colors.
|
|
338
355
|
*
|
|
339
356
|
* It statically imports the primitives, so only browser clients should load
|
|
340
357
|
* this entry; host code and node tests use the core entry (`./index.ts`).
|
|
@@ -842,7 +859,8 @@ function ModelMenu(props) {
|
|
|
842
859
|
* sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
|
|
843
860
|
* elevation at portal z-index 1100. The trigger mirrors the composer's 28px
|
|
844
861
|
* model pill, chevron rotating open. MenuSurface paints fill and backdrop.
|
|
845
|
-
* Only --dsw
|
|
862
|
+
* Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
|
|
863
|
+
* color is a bug.
|
|
846
864
|
*/
|
|
847
865
|
const modelMenuCss = `
|
|
848
866
|
.dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
|
|
@@ -865,12 +883,14 @@ const modelMenuCss = `
|
|
|
865
883
|
display: flex;
|
|
866
884
|
}
|
|
867
885
|
.dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
868
|
-
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0
|
|
886
|
+
.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)); }
|
|
869
887
|
.dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
870
888
|
.dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
|
|
871
889
|
.dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
|
|
872
|
-
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform
|
|
890
|
+
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
|
|
873
891
|
.dsh-model-menu-chevronOpen { transform: rotate(180deg); }
|
|
892
|
+
/* Portal overlay: the host publishes no z-index token, so the card keeps the
|
|
893
|
+
* native menu layer's 1100 verbatim. */
|
|
874
894
|
.dsh-model-menu-menu {
|
|
875
895
|
z-index: 1100;
|
|
876
896
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
@@ -896,6 +916,8 @@ const modelMenuCss = `
|
|
|
896
916
|
.dsh-model-menu-searchWithQuery { padding-right: 34px; }
|
|
897
917
|
.dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
|
|
898
918
|
.dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
|
|
919
|
+
/* The clear button is a circle by geometry (border-radius: 50%), a shape the
|
|
920
|
+
* radius token scale does not describe. */
|
|
899
921
|
.dsh-model-menu-searchClear {
|
|
900
922
|
width: 24px;
|
|
901
923
|
height: 24px;
|
|
@@ -1143,6 +1165,17 @@ ${scope} {
|
|
|
1143
1165
|
/* --- Layout scaffolding. --- */
|
|
1144
1166
|
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
1145
1167
|
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
1168
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
1169
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
1170
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
1171
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
1172
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
1173
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
1174
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
1175
|
+
& .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; }
|
|
1176
|
+
@media (max-width: 560px) {
|
|
1177
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
1178
|
+
}
|
|
1146
1179
|
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1147
1180
|
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
1148
1181
|
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
@@ -1170,6 +1203,9 @@ ${scope} {
|
|
|
1170
1203
|
* official primitive, so this only owns the vertical rhythm. --- */
|
|
1171
1204
|
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
1172
1205
|
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
1206
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
1207
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
1208
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
1173
1209
|
& .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); }
|
|
1174
1210
|
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
1175
1211
|
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
@@ -1360,7 +1396,7 @@ ${scope} {
|
|
|
1360
1396
|
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
1361
1397
|
}
|
|
1362
1398
|
}
|
|
1363
|
-
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(
|
|
1399
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
1364
1400
|
}
|
|
1365
1401
|
//#endregion
|
|
1366
1402
|
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
@@ -1376,6 +1412,37 @@ function modelSettingsChangesIdentity(packageName) {
|
|
|
1376
1412
|
namespace: segment
|
|
1377
1413
|
};
|
|
1378
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Shared "Session Management" settings section id. The section is provided by
|
|
1417
|
+
* shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),
|
|
1418
|
+
* not by any one business plugin: every participant registers only a row into
|
|
1419
|
+
* {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.
|
|
1420
|
+
*/
|
|
1421
|
+
const SESSION_SETTINGS_SECTION_ID = "session-manager";
|
|
1422
|
+
/**
|
|
1423
|
+
* Child list slot declared by the shared Session Management page. Neutral
|
|
1424
|
+
* naming on purpose: participants never import each other, they only
|
|
1425
|
+
* contribute `{id, order}` rows into this slot while the page is alive.
|
|
1426
|
+
*/
|
|
1427
|
+
const SESSION_SETTINGS_ITEM_SLOT = "dsh.session-settings.item";
|
|
1428
|
+
/**
|
|
1429
|
+
* Host-owned nav icon seat: a root-scoped list where each entry's `id` must
|
|
1430
|
+
* equal the `settings.section` id it decorates. Owner props are
|
|
1431
|
+
* `{size, active}`; icons stay `aria-hidden` so the section label remains the
|
|
1432
|
+
* accessible name. Contributors into a shared section may register the same
|
|
1433
|
+
* icon `id` with different priorities — the ledger keeps every entry while the
|
|
1434
|
+
* host renders one icon per section.
|
|
1435
|
+
*/
|
|
1436
|
+
const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
|
|
1437
|
+
/** Authenticated host-RPC channel for get/set of the shared default. */
|
|
1438
|
+
const AUXILIARY_MODEL_CHANNEL = "/dsh-auxiliary-model";
|
|
1439
|
+
/** Model-settings namespace under which the shared default is published. */
|
|
1440
|
+
const AUXILIARY_MODEL_NS = "@klarkxy/dsh-plugin-kit/auxiliary-model";
|
|
1441
|
+
/**
|
|
1442
|
+
* Typert identity of the shared change stream. Mounted on the root context
|
|
1443
|
+
* with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.
|
|
1444
|
+
*/
|
|
1445
|
+
const AUXILIARY_MODEL_CHANGES_IDENTITY = modelSettingsChangesIdentity("@klarkxy/dsh-plugin-kit");
|
|
1379
1446
|
//#endregion
|
|
1380
1447
|
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
1381
1448
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
@@ -1545,6 +1612,604 @@ function createModelSettingsSubscriber(ctx, packageName, identity = modelSetting
|
|
|
1545
1612
|
} });
|
|
1546
1613
|
}
|
|
1547
1614
|
//#endregion
|
|
1615
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
1616
|
+
/** One `<svg>` with the shared icon chrome; children are plain elements. */
|
|
1617
|
+
function lineIcon(children) {
|
|
1618
|
+
return function SettingsSectionIcon(props) {
|
|
1619
|
+
return (0, react.createElement)("svg", {
|
|
1620
|
+
"aria-hidden": "true",
|
|
1621
|
+
width: props.size ?? 16,
|
|
1622
|
+
height: props.size ?? 16,
|
|
1623
|
+
viewBox: "0 0 16 16",
|
|
1624
|
+
fill: "none",
|
|
1625
|
+
stroke: "currentColor",
|
|
1626
|
+
strokeWidth: 1,
|
|
1627
|
+
strokeLinecap: "round",
|
|
1628
|
+
strokeLinejoin: "round"
|
|
1629
|
+
}, ...children.map(([tag, attrs], index) => (0, react.createElement)(tag, {
|
|
1630
|
+
key: index,
|
|
1631
|
+
...attrs
|
|
1632
|
+
})));
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/** Chat bubble for the shared Session Management section. */
|
|
1636
|
+
const IconSessionSettings = lineIcon([["path", { d: "M8.5 13a5.5 5.5 0 0 0 5.5-5.5A5.5 5.5 0 0 0 8.5 2 5.5 5.5 0 0 0 3 7.5a5.48 5.48 0 0 0 .9 3L2.8 12.6a.5.5 0 0 0 .62.62l2.9-.98a5.48 5.48 0 0 0 2.18.76Z" }]]);
|
|
1637
|
+
/** Private Cordis rendezvous; every bundled copy of this helper resolves the same root service. */
|
|
1638
|
+
const SESSION_SETTINGS_PAGE_SERVICE = "dshSessionSettingsPage";
|
|
1639
|
+
/** The page component renders only its declared child list: items paint their own titles and styles. */
|
|
1640
|
+
function SessionSettingsPageView(props) {
|
|
1641
|
+
return props.renderSlot(SESSION_SETTINGS_ITEM_SLOT, {});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Acquire the root-owned page service, creating and providing it on first use.
|
|
1645
|
+
* One counter covers participants, the section wait and the provider: the last
|
|
1646
|
+
* release removes the wait and then the provider synchronously (Cordis deletes
|
|
1647
|
+
* the impl before its async disposer awaits), so a later acquire sees a clean
|
|
1648
|
+
* get() miss and can re-establish the page.
|
|
1649
|
+
*/
|
|
1650
|
+
function acquireSessionSettingsPage(root) {
|
|
1651
|
+
const existing = root.get(SESSION_SETTINGS_PAGE_SERVICE, false);
|
|
1652
|
+
if (existing) return existing;
|
|
1653
|
+
const slots = root.slots;
|
|
1654
|
+
let users = 0;
|
|
1655
|
+
let stopPage;
|
|
1656
|
+
let unprovide;
|
|
1657
|
+
const page = {
|
|
1658
|
+
retain() {
|
|
1659
|
+
users += 1;
|
|
1660
|
+
if (!stopPage) stopPage = slots.inject("settings.section", () => slots.register({
|
|
1661
|
+
name: "settings.section",
|
|
1662
|
+
id: SESSION_SETTINGS_SECTION_ID,
|
|
1663
|
+
order: 30,
|
|
1664
|
+
label: () => {
|
|
1665
|
+
const locale = root.get("locale", false);
|
|
1666
|
+
return String(locale?.getSnapshot?.().active ?? "").startsWith("zh") ? "会话管理" : "Session Management";
|
|
1667
|
+
},
|
|
1668
|
+
children: { [SESSION_SETTINGS_ITEM_SLOT]: {
|
|
1669
|
+
kind: "list",
|
|
1670
|
+
scope: "root"
|
|
1671
|
+
} }
|
|
1672
|
+
}, SessionSettingsPageView));
|
|
1673
|
+
},
|
|
1674
|
+
release() {
|
|
1675
|
+
if (users <= 0) return;
|
|
1676
|
+
users -= 1;
|
|
1677
|
+
if (users > 0) return;
|
|
1678
|
+
stopPage?.();
|
|
1679
|
+
stopPage = void 0;
|
|
1680
|
+
unprovide?.();
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
unprovide = root.provide(SESSION_SETTINGS_PAGE_SERVICE, page);
|
|
1684
|
+
return page;
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Contribute one row to the shared Session Management settings page.
|
|
1688
|
+
*
|
|
1689
|
+
* `ctx` is the plugin's own context: the row's slot wait unloads with the
|
|
1690
|
+
* plugin, while the page itself is retained on the shared root context for as
|
|
1691
|
+
* long as any contributor lives. There is no fallback seat — when every
|
|
1692
|
+
* contributor is gone the section disappears with the page. Returns a disposer.
|
|
1693
|
+
* Shared root-owned infrastructure (the auxiliary-model card) calls this on
|
|
1694
|
+
* `ctx.root` so its row, icon and page share are owned by the root lifetime
|
|
1695
|
+
* and released by the returned disposer, not by any plugin fiber.
|
|
1696
|
+
*/
|
|
1697
|
+
function registerSessionSettingsItem(ctx, item, component) {
|
|
1698
|
+
const slots = ctx.slots;
|
|
1699
|
+
const root = ctx.root;
|
|
1700
|
+
const page = acquireSessionSettingsPage(root);
|
|
1701
|
+
let stopItem;
|
|
1702
|
+
let stopIcon;
|
|
1703
|
+
try {
|
|
1704
|
+
page.retain();
|
|
1705
|
+
stopItem = slots.inject(SESSION_SETTINGS_ITEM_SLOT, () => slots.register({
|
|
1706
|
+
name: SESSION_SETTINGS_ITEM_SLOT,
|
|
1707
|
+
...item
|
|
1708
|
+
}, component));
|
|
1709
|
+
stopIcon = slots.inject(SETTINGS_SECTION_ICON_SLOT, () => slots.register({
|
|
1710
|
+
name: SETTINGS_SECTION_ICON_SLOT,
|
|
1711
|
+
id: SESSION_SETTINGS_SECTION_ID,
|
|
1712
|
+
order: item.order,
|
|
1713
|
+
priority: item.order
|
|
1714
|
+
}, IconSessionSettings));
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
stopIcon?.();
|
|
1717
|
+
stopItem?.();
|
|
1718
|
+
page.release();
|
|
1719
|
+
throw error;
|
|
1720
|
+
}
|
|
1721
|
+
let disposed = false;
|
|
1722
|
+
return () => {
|
|
1723
|
+
if (disposed) return;
|
|
1724
|
+
disposed = true;
|
|
1725
|
+
stopIcon?.();
|
|
1726
|
+
stopItem?.();
|
|
1727
|
+
page.release();
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region ../dsh-plugin-kit/src/auxiliary-model-client.tsx
|
|
1732
|
+
const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
|
|
1733
|
+
const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
|
|
1734
|
+
/** Private Cordis rendezvous; every bundled copy resolves the same root service. */
|
|
1735
|
+
const AUXILIARY_MODEL_CARD_SERVICE = "dshAuxiliaryModelSettingsCard";
|
|
1736
|
+
/** Theme-token-only stylesheet: officialUiCss scope plus the shared ModelMenu recipe. */
|
|
1737
|
+
const auxiliaryModelCss = `${officialUiCss("ams-root")}${modelMenuCss}
|
|
1738
|
+
.ams-root .ams-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
|
|
1739
|
+
`;
|
|
1740
|
+
const auxiliaryCopy = {
|
|
1741
|
+
zh: {
|
|
1742
|
+
title: "默认辅助模型",
|
|
1743
|
+
help: "各辅助功能(如标题生成、摘要)没有单独指定模型时使用的模型;仅影响声明使用它的功能。",
|
|
1744
|
+
model: "辅助模型",
|
|
1745
|
+
modelDefault: "跟随会话或宿主默认",
|
|
1746
|
+
modelHint: "不选择模型时,各功能按自己的规则决定对话模型。",
|
|
1747
|
+
effortDefault: "跟随模型默认",
|
|
1748
|
+
loading: "正在读取设置…",
|
|
1749
|
+
failed: "无法读取模型设置。",
|
|
1750
|
+
stale: "设置已被其他窗口更新,请刷新后重试。",
|
|
1751
|
+
saveFailed: "保存失败,原设置未更改。请重试。",
|
|
1752
|
+
saved: "已保存。",
|
|
1753
|
+
retry: "重试",
|
|
1754
|
+
refresh: "刷新设置",
|
|
1755
|
+
syncUnavailable: "自动刷新不可用,请手动刷新设置。"
|
|
1756
|
+
},
|
|
1757
|
+
en: {
|
|
1758
|
+
title: "Default Auxiliary Model",
|
|
1759
|
+
help: "The model used by auxiliary features (such as title generation or summarizing) that do not name their own model; only features that opted in are affected.",
|
|
1760
|
+
model: "Auxiliary model",
|
|
1761
|
+
modelDefault: "Follow session or host default",
|
|
1762
|
+
modelHint: "With no model selected, each feature follows its own rules for the conversation model.",
|
|
1763
|
+
effortDefault: "Follow model default",
|
|
1764
|
+
loading: "Loading settings…",
|
|
1765
|
+
failed: "Could not load model settings.",
|
|
1766
|
+
stale: "Settings were changed elsewhere; refresh and retry.",
|
|
1767
|
+
saveFailed: "Save failed; the previous setting was not changed. Try again.",
|
|
1768
|
+
saved: "Saved.",
|
|
1769
|
+
retry: "Retry",
|
|
1770
|
+
refresh: "Refresh settings",
|
|
1771
|
+
syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
/** A failed RPC keeps its code so the card can translate known rejections. */
|
|
1775
|
+
var AuxiliaryRpcError = class extends Error {
|
|
1776
|
+
code;
|
|
1777
|
+
constructor(code, message) {
|
|
1778
|
+
super(message);
|
|
1779
|
+
this.code = code;
|
|
1780
|
+
this.name = "AuxiliaryRpcError";
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
/** Host UI language, following live switches with the host's English fallback. */
|
|
1784
|
+
function useAuxiliaryLocale(client) {
|
|
1785
|
+
const subscribe = (0, react.useCallback)((listener) => client.locale?.subscribe(listener) ?? (() => {}), [client]);
|
|
1786
|
+
const snapshot = (0, react.useCallback)(() => client.locale?.getSnapshot().active ?? "en", [client]);
|
|
1787
|
+
return String((0, react.useSyncExternalStore)(subscribe, snapshot, snapshot)).startsWith("zh") ? "zh" : "en";
|
|
1788
|
+
}
|
|
1789
|
+
function isAbort(error) {
|
|
1790
|
+
return error instanceof Error && (error.name === "AbortError" || /abort|cancel/i.test(error.message));
|
|
1791
|
+
}
|
|
1792
|
+
function isStaleRejection(error) {
|
|
1793
|
+
if (!(error instanceof AuxiliaryRpcError)) return false;
|
|
1794
|
+
return error.code === "stale" || error.code === "auxiliary-model/conflict" || /changed|revision|reload|stale|conflict/i.test(error.message);
|
|
1795
|
+
}
|
|
1796
|
+
async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
1797
|
+
const result = await client.connection.rpc.call(AUXILIARY_MODEL_CHANNEL, endpoint, payload);
|
|
1798
|
+
if (!result || result.ok !== true) {
|
|
1799
|
+
const error = result && "error" in result ? result.error : void 0;
|
|
1800
|
+
throw new AuxiliaryRpcError(error?.code ?? "unknown", error?.message ?? `RPC ${endpoint} failed`);
|
|
1801
|
+
}
|
|
1802
|
+
return result.value;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* The shared card. All state comes from the shared host service over the
|
|
1806
|
+
* plugin-kit RPC channel; the card only renders the stored row.
|
|
1807
|
+
*/
|
|
1808
|
+
function AuxiliaryModelCard({ client, subscribe }) {
|
|
1809
|
+
const language = useAuxiliaryLocale(client);
|
|
1810
|
+
const text = auxiliaryCopy[language];
|
|
1811
|
+
const [settings, setSettings] = (0, react.useState)();
|
|
1812
|
+
const catalogState = useModelCatalog((0, react.useCallback)(() => readModelCatalog(client), [client]), { subscribe: (0, react.useCallback)((change) => client.connection.generation?.subscribe(change), [client]) });
|
|
1813
|
+
const { refresh: refreshCatalog } = catalogState;
|
|
1814
|
+
const choices = parseModelMenuChoices(catalogState.catalog, settings?.model ?? void 0);
|
|
1815
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
1816
|
+
const [error, setError] = (0, react.useState)("");
|
|
1817
|
+
const [note, setNote] = (0, react.useState)(false);
|
|
1818
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1819
|
+
const [syncError, setSyncError] = (0, react.useState)("");
|
|
1820
|
+
const operation = (0, react.useRef)({
|
|
1821
|
+
epoch: 0,
|
|
1822
|
+
active: false,
|
|
1823
|
+
reading: false,
|
|
1824
|
+
writing: false,
|
|
1825
|
+
pending: false
|
|
1826
|
+
});
|
|
1827
|
+
const refreshRef = (0, react.useRef)(() => {});
|
|
1828
|
+
const refresh = () => {
|
|
1829
|
+
const state = operation.current;
|
|
1830
|
+
if (!state.active) return;
|
|
1831
|
+
if (state.reading || state.writing) {
|
|
1832
|
+
state.pending = true;
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
state.pending = false;
|
|
1836
|
+
state.reading = true;
|
|
1837
|
+
setBusy(true);
|
|
1838
|
+
const epoch = state.epoch;
|
|
1839
|
+
callAuxiliaryRpc(client, "defaults.get").then((next) => {
|
|
1840
|
+
if (state.active && state.epoch === epoch && !state.pending) {
|
|
1841
|
+
setSettings(next);
|
|
1842
|
+
setError("");
|
|
1843
|
+
}
|
|
1844
|
+
}).catch((reason) => {
|
|
1845
|
+
if (state.active && state.epoch === epoch && !state.pending && !isAbort(reason)) setError("failed");
|
|
1846
|
+
}).finally(() => {
|
|
1847
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
1848
|
+
state.reading = false;
|
|
1849
|
+
setBusy(false);
|
|
1850
|
+
if (state.pending) refreshRef.current();
|
|
1851
|
+
});
|
|
1852
|
+
};
|
|
1853
|
+
refreshRef.current = refresh;
|
|
1854
|
+
(0, react.useEffect)(() => {
|
|
1855
|
+
const state = operation.current;
|
|
1856
|
+
state.epoch++;
|
|
1857
|
+
state.active = true;
|
|
1858
|
+
state.reading = false;
|
|
1859
|
+
state.writing = false;
|
|
1860
|
+
state.pending = false;
|
|
1861
|
+
setBusy(false);
|
|
1862
|
+
setSettings(void 0);
|
|
1863
|
+
setError("");
|
|
1864
|
+
setNote(false);
|
|
1865
|
+
refreshRef.current();
|
|
1866
|
+
return () => {
|
|
1867
|
+
state.active = false;
|
|
1868
|
+
state.epoch++;
|
|
1869
|
+
};
|
|
1870
|
+
}, [client]);
|
|
1871
|
+
(0, react.useEffect)(() => {
|
|
1872
|
+
setSyncError("");
|
|
1873
|
+
let live = true;
|
|
1874
|
+
let stop;
|
|
1875
|
+
try {
|
|
1876
|
+
stop = subscribe?.(() => {
|
|
1877
|
+
if (live) {
|
|
1878
|
+
setSyncError("");
|
|
1879
|
+
refreshCatalog();
|
|
1880
|
+
refreshRef.current();
|
|
1881
|
+
}
|
|
1882
|
+
}, (message) => {
|
|
1883
|
+
if (live) setSyncError(message);
|
|
1884
|
+
});
|
|
1885
|
+
} catch {
|
|
1886
|
+
if (live) setSyncError("Model settings sync service unavailable");
|
|
1887
|
+
}
|
|
1888
|
+
return () => {
|
|
1889
|
+
live = false;
|
|
1890
|
+
try {
|
|
1891
|
+
stop?.();
|
|
1892
|
+
} catch {}
|
|
1893
|
+
};
|
|
1894
|
+
}, [subscribe, refreshCatalog]);
|
|
1895
|
+
const save = (0, react.useCallback)((model) => {
|
|
1896
|
+
const state = operation.current;
|
|
1897
|
+
if (!settings || !state.active || state.writing || state.reading) return;
|
|
1898
|
+
state.writing = true;
|
|
1899
|
+
const epoch = state.epoch;
|
|
1900
|
+
setBusy(true);
|
|
1901
|
+
setNote(false);
|
|
1902
|
+
setError("");
|
|
1903
|
+
callAuxiliaryRpc(client, "defaults.set", {
|
|
1904
|
+
model: model ?? {
|
|
1905
|
+
provider: "",
|
|
1906
|
+
model: ""
|
|
1907
|
+
},
|
|
1908
|
+
expectedRevision: settings.revision
|
|
1909
|
+
}).then((next) => {
|
|
1910
|
+
if (state.active && state.epoch === epoch) {
|
|
1911
|
+
setSettings(next);
|
|
1912
|
+
setNote(true);
|
|
1913
|
+
refreshCatalog();
|
|
1914
|
+
}
|
|
1915
|
+
}).catch((reason) => {
|
|
1916
|
+
if (state.active && state.epoch === epoch) setError(isStaleRejection(reason) ? "stale" : "saveFailed");
|
|
1917
|
+
}).finally(() => {
|
|
1918
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
1919
|
+
state.writing = false;
|
|
1920
|
+
setBusy(false);
|
|
1921
|
+
if (state.pending) refreshRef.current();
|
|
1922
|
+
});
|
|
1923
|
+
}, [
|
|
1924
|
+
client,
|
|
1925
|
+
settings,
|
|
1926
|
+
refreshCatalog
|
|
1927
|
+
]);
|
|
1928
|
+
const locked = busy;
|
|
1929
|
+
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1930
|
+
className: "ams-root dsh-ui-card",
|
|
1931
|
+
"data-testid": "auxiliary-model-settings",
|
|
1932
|
+
children: [
|
|
1933
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1934
|
+
className: "dsh-ui-heading",
|
|
1935
|
+
children: text.title
|
|
1936
|
+
}),
|
|
1937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1938
|
+
className: "dsh-ui-help",
|
|
1939
|
+
children: text.help
|
|
1940
|
+
}),
|
|
1941
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1942
|
+
role: "alert",
|
|
1943
|
+
className: "dsh-ui-error",
|
|
1944
|
+
children: text[error]
|
|
1945
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1946
|
+
role: "status",
|
|
1947
|
+
className: "dsh-ui-hint",
|
|
1948
|
+
children: text.loading
|
|
1949
|
+
}),
|
|
1950
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1951
|
+
role: "status",
|
|
1952
|
+
className: "dsh-ui-hint",
|
|
1953
|
+
children: text.syncUnavailable
|
|
1954
|
+
}),
|
|
1955
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1956
|
+
className: "dsh-ui-actions",
|
|
1957
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1958
|
+
variant: "ghost",
|
|
1959
|
+
size: "sm",
|
|
1960
|
+
disabled: locked,
|
|
1961
|
+
onClick: () => refreshRef.current(),
|
|
1962
|
+
children: text.retry
|
|
1963
|
+
})
|
|
1964
|
+
})
|
|
1965
|
+
]
|
|
1966
|
+
});
|
|
1967
|
+
const current = settings.model ?? {
|
|
1968
|
+
provider: "",
|
|
1969
|
+
model: ""
|
|
1970
|
+
};
|
|
1971
|
+
const follow = !current.provider || !current.model;
|
|
1972
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
1973
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
1974
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
1975
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
1976
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
1977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1978
|
+
className: "ams-root dsh-ui-card",
|
|
1979
|
+
"data-testid": "auxiliary-model-settings",
|
|
1980
|
+
children: [
|
|
1981
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1982
|
+
className: "dsh-ui-heading",
|
|
1983
|
+
children: text.title
|
|
1984
|
+
}),
|
|
1985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1986
|
+
className: "dsh-ui-help",
|
|
1987
|
+
children: text.help
|
|
1988
|
+
}),
|
|
1989
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1990
|
+
className: "dsh-ui-field",
|
|
1991
|
+
children: [
|
|
1992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1993
|
+
className: "dsh-ui-label",
|
|
1994
|
+
children: text.model
|
|
1995
|
+
}),
|
|
1996
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
1997
|
+
state: catalogState,
|
|
1998
|
+
locale: language
|
|
1999
|
+
}),
|
|
2000
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
2001
|
+
locale: language,
|
|
2002
|
+
open: menuOpen && !locked,
|
|
2003
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2004
|
+
listClassName: "ams-root ams-menu-portal",
|
|
2005
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2006
|
+
type: "button",
|
|
2007
|
+
className: "dsh-model-menu-trigger",
|
|
2008
|
+
disabled: locked,
|
|
2009
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2010
|
+
children: [
|
|
2011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2012
|
+
className: "dsh-model-menu-triggerLabel",
|
|
2013
|
+
children: currentLabel
|
|
2014
|
+
}),
|
|
2015
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2016
|
+
className: "dsh-model-menu-triggerEffort",
|
|
2017
|
+
children: effortName ?? text.effortDefault
|
|
2018
|
+
}) : null,
|
|
2019
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2020
|
+
]
|
|
2021
|
+
}),
|
|
2022
|
+
choices,
|
|
2023
|
+
selected: follow ? void 0 : {
|
|
2024
|
+
provider: current.provider,
|
|
2025
|
+
model: current.model
|
|
2026
|
+
},
|
|
2027
|
+
onPick: (picked) => save({
|
|
2028
|
+
provider: picked.provider,
|
|
2029
|
+
model: picked.model
|
|
2030
|
+
}),
|
|
2031
|
+
leading: [{
|
|
2032
|
+
id: "default",
|
|
2033
|
+
label: text.modelDefault,
|
|
2034
|
+
selected: follow
|
|
2035
|
+
}],
|
|
2036
|
+
onPickLeading: () => save(null),
|
|
2037
|
+
efforts,
|
|
2038
|
+
selectedEffort: current.reasoningEffort || "",
|
|
2039
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
2040
|
+
provider: current.provider,
|
|
2041
|
+
model: current.model,
|
|
2042
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
2043
|
+
}),
|
|
2044
|
+
modelLabel: text.model,
|
|
2045
|
+
modelValue: currentLabel,
|
|
2046
|
+
defaultEffortLabel: text.effortDefault
|
|
2047
|
+
}),
|
|
2048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2049
|
+
className: "dsh-ui-hint",
|
|
2050
|
+
children: text.modelHint
|
|
2051
|
+
})
|
|
2052
|
+
]
|
|
2053
|
+
}),
|
|
2054
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2055
|
+
className: "ams-row",
|
|
2056
|
+
children: [
|
|
2057
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2058
|
+
role: "status",
|
|
2059
|
+
className: "dsh-ui-hint",
|
|
2060
|
+
children: text.syncUnavailable
|
|
2061
|
+
}),
|
|
2062
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2063
|
+
role: "alert",
|
|
2064
|
+
className: "dsh-ui-error",
|
|
2065
|
+
children: text[error]
|
|
2066
|
+
}),
|
|
2067
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2068
|
+
role: "status",
|
|
2069
|
+
className: "dsh-ui-notice",
|
|
2070
|
+
children: text.saved
|
|
2071
|
+
}),
|
|
2072
|
+
error || syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2073
|
+
variant: "ghost",
|
|
2074
|
+
size: "sm",
|
|
2075
|
+
disabled: locked,
|
|
2076
|
+
onClick: () => refreshRef.current(),
|
|
2077
|
+
children: text.refresh
|
|
2078
|
+
}) : null
|
|
2079
|
+
]
|
|
2080
|
+
})
|
|
2081
|
+
]
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
const AUXILIARY_MODEL_WATCH_COORDINATOR = "dshAuxiliaryModelWatchCoordinator";
|
|
2085
|
+
function acquireWatchCoordinator(root) {
|
|
2086
|
+
const existing = root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false);
|
|
2087
|
+
if (existing) return existing;
|
|
2088
|
+
let unprovide;
|
|
2089
|
+
const coordinator = {
|
|
2090
|
+
tail: Promise.resolve(),
|
|
2091
|
+
active: 0,
|
|
2092
|
+
mount() {
|
|
2093
|
+
coordinator.active += 1;
|
|
2094
|
+
const previousTail = coordinator.tail;
|
|
2095
|
+
const lease = {
|
|
2096
|
+
closed: false,
|
|
2097
|
+
done() {
|
|
2098
|
+
if (lease.closed) return;
|
|
2099
|
+
lease.closed = true;
|
|
2100
|
+
coordinator.active -= 1;
|
|
2101
|
+
const owned = lease.fiber;
|
|
2102
|
+
lease.fiber = void 0;
|
|
2103
|
+
coordinator.tail = coordinator.tail.then(() => Promise.resolve(owned?.dispose()).catch(() => {}));
|
|
2104
|
+
coordinator.tail.then(() => {
|
|
2105
|
+
if (coordinator.active > 0) return;
|
|
2106
|
+
try {
|
|
2107
|
+
if (root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) === coordinator) Promise.resolve(unprovide?.()).catch(() => {});
|
|
2108
|
+
} catch {}
|
|
2109
|
+
});
|
|
2110
|
+
},
|
|
2111
|
+
fiber: void 0
|
|
2112
|
+
};
|
|
2113
|
+
previousTail.then(() => {
|
|
2114
|
+
if (lease.closed) return;
|
|
2115
|
+
try {
|
|
2116
|
+
lease.fiber = root.inject(["remote"], (injected) => {
|
|
2117
|
+
const scope = injected;
|
|
2118
|
+
const mounting = scope.remote.$mount(modelSettingsChangesRemote(AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY));
|
|
2119
|
+
mounting.catch(() => {});
|
|
2120
|
+
scope.effect(() => async () => {
|
|
2121
|
+
await (await mounting.catch(() => void 0))?.();
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
} catch {}
|
|
2125
|
+
});
|
|
2126
|
+
return lease;
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
unprovide = root.provide(AUXILIARY_MODEL_WATCH_COORDINATOR, coordinator);
|
|
2130
|
+
return coordinator;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Acquire the root-owned card service, creating it on first use. One counter
|
|
2134
|
+
* covers participants, the seat wait and the change-stream mount: the last
|
|
2135
|
+
* release removes the wait, then the watch contribution, then the provider
|
|
2136
|
+
* itself synchronously (Cordis deletes the impl before its async disposer
|
|
2137
|
+
* awaits), so a later acquire sees a clean get() miss and can re-establish.
|
|
2138
|
+
*/
|
|
2139
|
+
function acquireAuxiliaryModelCard(root, ctx) {
|
|
2140
|
+
const existing = root.get(AUXILIARY_MODEL_CARD_SERVICE, false);
|
|
2141
|
+
if (existing) return existing;
|
|
2142
|
+
let users = 0;
|
|
2143
|
+
let styleElement;
|
|
2144
|
+
let stopItem;
|
|
2145
|
+
let unprovide;
|
|
2146
|
+
const watchLease = acquireWatchCoordinator(root).mount();
|
|
2147
|
+
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
2148
|
+
const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
2149
|
+
client: root,
|
|
2150
|
+
subscribe: subscriber.subscribe
|
|
2151
|
+
});
|
|
2152
|
+
const service = {
|
|
2153
|
+
retain() {
|
|
2154
|
+
users += 1;
|
|
2155
|
+
if (styleElement === void 0 && typeof document !== "undefined") {
|
|
2156
|
+
styleElement = document.createElement("style");
|
|
2157
|
+
styleElement.setAttribute("data-plugin", "dsh-auxiliary-model-shared");
|
|
2158
|
+
styleElement.textContent = auxiliaryModelCss;
|
|
2159
|
+
document.head.appendChild(styleElement);
|
|
2160
|
+
}
|
|
2161
|
+
if (!stopItem) stopItem = registerSessionSettingsItem(ctx.root, {
|
|
2162
|
+
id: AUXILIARY_MODEL_SEAT_ID,
|
|
2163
|
+
order: 55
|
|
2164
|
+
}, render);
|
|
2165
|
+
},
|
|
2166
|
+
release() {
|
|
2167
|
+
if (users <= 0) return;
|
|
2168
|
+
users -= 1;
|
|
2169
|
+
if (users > 0) return;
|
|
2170
|
+
stopItem?.();
|
|
2171
|
+
stopItem = void 0;
|
|
2172
|
+
styleElement?.remove();
|
|
2173
|
+
styleElement = void 0;
|
|
2174
|
+
watchLease.done();
|
|
2175
|
+
try {
|
|
2176
|
+
Promise.resolve(unprovide?.()).catch(() => {});
|
|
2177
|
+
} catch {}
|
|
2178
|
+
unprovide = void 0;
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
unprovide = root.provide(AUXILIARY_MODEL_CARD_SERVICE, service);
|
|
2182
|
+
return service;
|
|
2183
|
+
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Show the shared "default auxiliary model" card while the calling plugin is
|
|
2186
|
+
* enabled.
|
|
2187
|
+
*
|
|
2188
|
+
* `ctx` is the plugin's own client context: the card itself is retained on the
|
|
2189
|
+
* shared root context for as long as any participant lives, so an arbitrary
|
|
2190
|
+
* subset of enabled plugins yields the same single card. The mount's slot wait
|
|
2191
|
+
* and the change-stream contribution are owned by the root service — the first
|
|
2192
|
+
* participant's unload cannot take the card down. Returns a disposer; it may
|
|
2193
|
+
* be invoked by both ctx.effect and an explicit caller and releases at most
|
|
2194
|
+
* once, so a double call cannot unbalance the shared refcount.
|
|
2195
|
+
*/
|
|
2196
|
+
function registerAuxiliaryModelSettings(ctx) {
|
|
2197
|
+
const root = ctx.root;
|
|
2198
|
+
const service = acquireAuxiliaryModelCard(root, ctx);
|
|
2199
|
+
try {
|
|
2200
|
+
service.retain();
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
service.release();
|
|
2203
|
+
throw error;
|
|
2204
|
+
}
|
|
2205
|
+
let disposed = false;
|
|
2206
|
+
return () => {
|
|
2207
|
+
if (disposed) return;
|
|
2208
|
+
disposed = true;
|
|
2209
|
+
service.release();
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
//#endregion
|
|
1548
2213
|
//#region src/contracts.ts
|
|
1549
2214
|
const PLUGIN_NAME = "@klarkxy/dsh-git-commit";
|
|
1550
2215
|
const RPC_CHANNEL = "/dsh-git-commit";
|
|
@@ -1565,12 +2230,11 @@ const zh = {
|
|
|
1565
2230
|
commitAction: "分次提交全部工作区修改",
|
|
1566
2231
|
files: "个文件",
|
|
1567
2232
|
model: "模型",
|
|
1568
|
-
modelDefault: "默认模型",
|
|
1569
|
-
modelCustom: "插件设置",
|
|
1570
2233
|
filesCount: "({n} 个文件)",
|
|
1571
2234
|
confirmPrompt: "将把 {n} 个文件的全部修改分次提交到当前分支。",
|
|
1572
2235
|
confirm: "确认提交",
|
|
1573
2236
|
cancel: "取消",
|
|
2237
|
+
commitBlocked: "正在保存模型设置或上次保存未生效,恢复后才可提交",
|
|
1574
2238
|
loading: "正在读取仓库状态…",
|
|
1575
2239
|
running: "正在整理修改并分次提交…",
|
|
1576
2240
|
done: "已创建提交",
|
|
@@ -1596,7 +2260,7 @@ const zh = {
|
|
|
1596
2260
|
workspaceChanged: "工作区或分支已变更,请刷新后重试提交",
|
|
1597
2261
|
settingsUnavailable: "无法读取提交设置,请重新加载插件后重试",
|
|
1598
2262
|
invalidRequest: "提交请求无效,请刷新后重试",
|
|
1599
|
-
planUnavailable: "
|
|
2263
|
+
planUnavailable: "模型没有给出可用的提交方案,已取消本次提交,工作区没有被改动。换一个模型重试,或开启下方「模型不可用时仍然提交」。",
|
|
1600
2264
|
close: "关闭"
|
|
1601
2265
|
};
|
|
1602
2266
|
const en = {
|
|
@@ -1605,12 +2269,11 @@ const en = {
|
|
|
1605
2269
|
commitAction: "Commit all workspace changes",
|
|
1606
2270
|
files: "files",
|
|
1607
2271
|
model: "Model",
|
|
1608
|
-
modelDefault: "default model",
|
|
1609
|
-
modelCustom: "Plugin settings",
|
|
1610
2272
|
filesCount: " ({n} files)",
|
|
1611
2273
|
confirmPrompt: "All changes in {n} files will be committed to the current branch in one or more commits.",
|
|
1612
2274
|
confirm: "Commit now",
|
|
1613
2275
|
cancel: "Cancel",
|
|
2276
|
+
commitBlocked: "A settings write is pending or unrecovered; commits unlock once it settles",
|
|
1614
2277
|
loading: "Reading repository status…",
|
|
1615
2278
|
running: "Organizing changes into commits…",
|
|
1616
2279
|
done: "Commits created",
|
|
@@ -1636,7 +2299,7 @@ const en = {
|
|
|
1636
2299
|
workspaceChanged: "The workspace or branch changed; refresh and retry the commit",
|
|
1637
2300
|
settingsUnavailable: "Could not read commit settings; reload the plugin and retry",
|
|
1638
2301
|
invalidRequest: "Invalid commit request; refresh and retry",
|
|
1639
|
-
planUnavailable: "The model returned no usable commit plan, so the run was cancelled and your workspace is untouched. Try another model, or
|
|
2302
|
+
planUnavailable: "The model returned no usable commit plan, so the run was cancelled and your workspace is untouched. Try another model, or enable “Commit even without a model plan” below.",
|
|
1640
2303
|
close: "Close"
|
|
1641
2304
|
};
|
|
1642
2305
|
/** A failed RPC keeps its code so the panel can explain a rejection in its own words. */
|
|
@@ -1740,7 +2403,7 @@ function GitBranchIcon() {
|
|
|
1740
2403
|
]
|
|
1741
2404
|
});
|
|
1742
2405
|
}
|
|
1743
|
-
function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
2406
|
+
function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSettings }) {
|
|
1744
2407
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1745
2408
|
const [status, setStatus] = (0, react.useState)();
|
|
1746
2409
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -1748,6 +2411,29 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1748
2411
|
const [result, setResult] = (0, react.useState)(null);
|
|
1749
2412
|
const [error, setError] = (0, react.useState)(null);
|
|
1750
2413
|
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
2414
|
+
const pendingWrites = (0, react.useRef)(0);
|
|
2415
|
+
const writeFailed = (0, react.useRef)(false);
|
|
2416
|
+
const settingsDirty = (0, react.useRef)(false);
|
|
2417
|
+
const [settingsPending, setSettingsPending] = (0, react.useState)(false);
|
|
2418
|
+
const [settingsSeen, setSettingsSeen] = (0, react.useState)(false);
|
|
2419
|
+
const syncLock = (0, react.useCallback)(() => {
|
|
2420
|
+
const locked = pendingWrites.current > 0 || writeFailed.current;
|
|
2421
|
+
settingsDirty.current = locked;
|
|
2422
|
+
setSettingsPending(locked);
|
|
2423
|
+
}, []);
|
|
2424
|
+
const markWrite = (0, react.useCallback)((active) => {
|
|
2425
|
+
pendingWrites.current = Math.max(0, pendingWrites.current + (active ? 1 : -1));
|
|
2426
|
+
syncLock();
|
|
2427
|
+
}, [syncLock]);
|
|
2428
|
+
const markWriteFailed = (0, react.useCallback)(() => {
|
|
2429
|
+
writeFailed.current = true;
|
|
2430
|
+
syncLock();
|
|
2431
|
+
}, [syncLock]);
|
|
2432
|
+
const markSeen = (0, react.useCallback)(() => {
|
|
2433
|
+
writeFailed.current = false;
|
|
2434
|
+
setSettingsSeen(true);
|
|
2435
|
+
syncLock();
|
|
2436
|
+
}, [syncLock]);
|
|
1751
2437
|
const rootRef = (0, react.useRef)(null);
|
|
1752
2438
|
const triggerRef = (0, react.useRef)(null);
|
|
1753
2439
|
const panelRef = (0, react.useRef)(null);
|
|
@@ -1760,19 +2446,26 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1760
2446
|
margin: 16
|
|
1761
2447
|
});
|
|
1762
2448
|
const sessionRunning = useSession((snapshot) => snapshot.running === true);
|
|
2449
|
+
const panelPortalRef = (0, react.useMemo)(() => ({ get current() {
|
|
2450
|
+
const panel = panelRef.current;
|
|
2451
|
+
if (!panel) return null;
|
|
2452
|
+
return { contains: (node) => panel.contains(node) || [...document.querySelectorAll(".gcm-menu-portal")].some((menu) => menu.contains(node)) };
|
|
2453
|
+
} }), []);
|
|
1763
2454
|
const changeOpen = (0, react.useCallback)((next) => {
|
|
2455
|
+
if (!next && pendingWrites.current > 0) return;
|
|
1764
2456
|
setOpen(next);
|
|
1765
2457
|
setConfirming(false);
|
|
1766
2458
|
if (next) {
|
|
1767
2459
|
setResult(null);
|
|
1768
2460
|
setError(null);
|
|
2461
|
+
setSettingsSeen(false);
|
|
1769
2462
|
}
|
|
1770
2463
|
}, []);
|
|
1771
2464
|
const positioned = Boolean(position);
|
|
1772
2465
|
(0, react.useEffect)(() => {
|
|
1773
2466
|
if (open && positioned) panelRef.current?.focus();
|
|
1774
2467
|
}, [open, positioned]);
|
|
1775
|
-
(0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, changeOpen,
|
|
2468
|
+
(0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, changeOpen, panelPortalRef);
|
|
1776
2469
|
(0, react.useEffect)(() => {
|
|
1777
2470
|
generationRef.current += 1;
|
|
1778
2471
|
setOpen(false);
|
|
@@ -1780,6 +2473,11 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1780
2473
|
setResult(null);
|
|
1781
2474
|
setError(null);
|
|
1782
2475
|
setRunning(false);
|
|
2476
|
+
pendingWrites.current = 0;
|
|
2477
|
+
writeFailed.current = false;
|
|
2478
|
+
settingsDirty.current = false;
|
|
2479
|
+
setSettingsPending(false);
|
|
2480
|
+
setSettingsSeen(false);
|
|
1783
2481
|
}, [sessionId]);
|
|
1784
2482
|
const loadStatus = (0, react.useCallback)(async () => {
|
|
1785
2483
|
const generation = generationRef.current;
|
|
@@ -1814,6 +2512,10 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1814
2512
|
}, [open, changeOpen]);
|
|
1815
2513
|
const runCommit = (0, react.useCallback)(async () => {
|
|
1816
2514
|
const generation = generationRef.current;
|
|
2515
|
+
if (settingsDirty.current || client && !settingsSeen) {
|
|
2516
|
+
setConfirming(false);
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
1817
2519
|
setConfirming(false);
|
|
1818
2520
|
setRunning(true);
|
|
1819
2521
|
setResult(null);
|
|
@@ -1835,10 +2537,15 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1835
2537
|
}
|
|
1836
2538
|
}
|
|
1837
2539
|
}
|
|
1838
|
-
}, [
|
|
2540
|
+
}, [
|
|
2541
|
+
call,
|
|
2542
|
+
sessionId,
|
|
2543
|
+
client,
|
|
2544
|
+
settingsSeen
|
|
2545
|
+
]);
|
|
1839
2546
|
const available = status?.available === true;
|
|
1840
2547
|
const workspaceCount = status?.workspace.files ?? 0;
|
|
1841
|
-
const canCommit = available && !running && !sessionRunning && workspaceCount > 0;
|
|
2548
|
+
const canCommit = available && !running && !sessionRunning && workspaceCount > 0 && !settingsPending && (!client || settingsSeen);
|
|
1842
2549
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1843
2550
|
ref: rootRef,
|
|
1844
2551
|
className: "gcm-root",
|
|
@@ -1908,33 +2615,21 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1908
2615
|
role: "alert",
|
|
1909
2616
|
children: errorText(t, error)
|
|
1910
2617
|
}),
|
|
2618
|
+
client ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitCommitSettingsPanel, {
|
|
2619
|
+
client,
|
|
2620
|
+
subscribeSettings,
|
|
2621
|
+
disabled: running,
|
|
2622
|
+
onSaving: markWrite,
|
|
2623
|
+
onWriteFailed: markWriteFailed,
|
|
2624
|
+
onReady: markSeen,
|
|
2625
|
+
onSaved: () => void loadStatus()
|
|
2626
|
+
}) : null,
|
|
1911
2627
|
status && !available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1912
2628
|
className: "dsh-ui-meta",
|
|
1913
2629
|
role: "status",
|
|
1914
2630
|
children: reasonText(t, status)
|
|
1915
2631
|
}),
|
|
1916
2632
|
available && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1917
|
-
status?.model ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1918
|
-
className: "gcm-model",
|
|
1919
|
-
children: [
|
|
1920
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1921
|
-
className: "dsh-ui-hint",
|
|
1922
|
-
children: t("model")
|
|
1923
|
-
}),
|
|
1924
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1925
|
-
className: "dsh-ui-truncate",
|
|
1926
|
-
children: [
|
|
1927
|
-
status.model.provider,
|
|
1928
|
-
"/",
|
|
1929
|
-
status.model.model
|
|
1930
|
-
]
|
|
1931
|
-
}),
|
|
1932
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1933
|
-
tone: status.model.source === "page" ? "info" : "neutral",
|
|
1934
|
-
children: status.model.source === "page" ? t("modelCustom") : t("modelDefault")
|
|
1935
|
-
})
|
|
1936
|
-
]
|
|
1937
|
-
}) : null,
|
|
1938
2633
|
confirming && canCommit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1939
2634
|
className: "dsh-ui-stack",
|
|
1940
2635
|
role: "group",
|
|
@@ -1970,6 +2665,11 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
1970
2665
|
sessionRunning && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1971
2666
|
className: "dsh-ui-hint",
|
|
1972
2667
|
children: t("sessionRunning")
|
|
2668
|
+
}),
|
|
2669
|
+
settingsPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2670
|
+
className: "dsh-ui-hint",
|
|
2671
|
+
role: "status",
|
|
2672
|
+
children: t("commitBlocked")
|
|
1973
2673
|
})
|
|
1974
2674
|
] }),
|
|
1975
2675
|
running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -2045,11 +2745,9 @@ const clientCss = `${officialUiCss("gcm-root")}${modelMenuCss}
|
|
|
2045
2745
|
* of at the trigger. Naming both classes puts this rule at the same weight,
|
|
2046
2746
|
* later in the sheet, so it wins. */
|
|
2047
2747
|
.gcm-root.gcm-panel { position: fixed; width: min(360px, calc(100vw - 32px)); max-height: min(560px, calc(100vh - 32px)); overflow: auto; }
|
|
2048
|
-
.gcm-
|
|
2049
|
-
.gcm-commits {
|
|
2050
|
-
.gcm-
|
|
2051
|
-
.gcm-settings { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
2052
|
-
.gcm-settings-status { display: flex; flex-direction: column; gap: 4px; }
|
|
2748
|
+
.gcm-root .gcm-commits { margin: 0; padding-inline-start: 18px; display: grid; gap: 2px; font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
|
2749
|
+
.gcm-root .gcm-commits code { font-size: 12px; background: var(--dsw-alias-bg-layer-2); padding: 0 4px; border-radius: var(--dsw-radius-sm); }
|
|
2750
|
+
.gcm-root .gcm-settings-status { display: flex; flex-direction: column; gap: 4px; }
|
|
2053
2751
|
`;
|
|
2054
2752
|
/** Host UI language, following live switches with the host's English fallback. */
|
|
2055
2753
|
function useHostLocale(client) {
|
|
@@ -2058,31 +2756,31 @@ function useHostLocale(client) {
|
|
|
2058
2756
|
return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
|
|
2059
2757
|
}
|
|
2060
2758
|
const settingsZh = {
|
|
2061
|
-
pageTitle: "Git 提交",
|
|
2062
2759
|
model: "提交规划模型",
|
|
2063
|
-
modelDefault: "
|
|
2064
|
-
modelHint: "
|
|
2760
|
+
modelDefault: "默认辅助模型",
|
|
2761
|
+
modelHint: "留空使用全局默认辅助模型;未设置时跟随当前会话模型,再回落到宿主默认对话模型。",
|
|
2065
2762
|
fallback: "模型不可用时仍然提交",
|
|
2066
2763
|
fallbackHint: "默认关闭:模型规划失败会取消本次提交,工作区不变。开启后全部改动会合并成一个提交,由插件代写提交信息。",
|
|
2067
2764
|
loading: "正在读取设置…",
|
|
2068
2765
|
saved: "已保存。",
|
|
2069
2766
|
stale: "设置已更新,请刷新后重试。",
|
|
2070
2767
|
failed: "无法读取提交设置。",
|
|
2768
|
+
saveFailed: "设置未能保存,已显示的选择未生效。请重试。",
|
|
2071
2769
|
retry: "重试",
|
|
2072
2770
|
refresh: "刷新设置",
|
|
2073
2771
|
syncUnavailable: "自动刷新不可用,请手动刷新设置。"
|
|
2074
2772
|
};
|
|
2075
2773
|
const settingsEn = {
|
|
2076
|
-
pageTitle: "Git Commit",
|
|
2077
2774
|
model: "Commit planning model",
|
|
2078
|
-
modelDefault: "Default model",
|
|
2079
|
-
modelHint: "Leave empty to use the current
|
|
2775
|
+
modelDefault: "Default auxiliary model",
|
|
2776
|
+
modelHint: "Leave empty to use the global default auxiliary model; when none is set, the current conversation model, then the host default chat model.",
|
|
2080
2777
|
fallback: "Commit even without a model plan",
|
|
2081
2778
|
fallbackHint: "Off by default: a failed plan cancels the run and leaves the workspace untouched. When on, every change lands in one commit with a plugin-written message.",
|
|
2082
2779
|
loading: "Loading settings…",
|
|
2083
2780
|
saved: "Saved.",
|
|
2084
2781
|
stale: "Settings changed; refresh and retry.",
|
|
2085
2782
|
failed: "Could not load commit settings.",
|
|
2783
|
+
saveFailed: "The change could not be saved, so the shown selection did not take effect. Try again.",
|
|
2086
2784
|
retry: "Retry",
|
|
2087
2785
|
refresh: "Refresh settings",
|
|
2088
2786
|
syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
|
|
@@ -2090,7 +2788,8 @@ const settingsEn = {
|
|
|
2090
2788
|
function settingsCopy(locale) {
|
|
2091
2789
|
return String(locale).startsWith("zh") ? settingsZh : settingsEn;
|
|
2092
2790
|
}
|
|
2093
|
-
/**
|
|
2791
|
+
/** Model/fallback block embedded in the commit popover; picks persist as the
|
|
2792
|
+
* plugin's saved defaults and sync through the model-settings change channel. */
|
|
2094
2793
|
function GitCommitSettingsPanel(props) {
|
|
2095
2794
|
const hostLocale = useHostLocale(props.client);
|
|
2096
2795
|
const text = settingsCopy(props.locale ?? hostLocale);
|
|
@@ -2128,6 +2827,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
2128
2827
|
if (state.active && state.epoch === epoch && !state.pending) {
|
|
2129
2828
|
setSettings(next);
|
|
2130
2829
|
setError("");
|
|
2830
|
+
props.onReady?.();
|
|
2131
2831
|
}
|
|
2132
2832
|
}).catch(() => {
|
|
2133
2833
|
if (state.active && state.epoch === epoch && !state.pending) setError("failed");
|
|
@@ -2177,14 +2877,16 @@ function GitCommitSettingsPanel(props) {
|
|
|
2177
2877
|
props.client,
|
|
2178
2878
|
refreshCatalog
|
|
2179
2879
|
]);
|
|
2880
|
+
const controlsLocked = props.disabled === true;
|
|
2180
2881
|
const save = (0, react.useCallback)((patch) => {
|
|
2181
2882
|
const state = operation.current;
|
|
2182
|
-
if (!settings || !state.active || state.writing || state.reading) return;
|
|
2883
|
+
if (!settings || !state.active || state.writing || state.reading || props.disabled) return;
|
|
2183
2884
|
state.writing = true;
|
|
2184
2885
|
const epoch = state.epoch;
|
|
2185
2886
|
setBusy(true);
|
|
2186
2887
|
setNote(false);
|
|
2187
2888
|
setError("");
|
|
2889
|
+
props.onSaving?.(true);
|
|
2188
2890
|
rpc(call, "settings.update", {
|
|
2189
2891
|
settings: {
|
|
2190
2892
|
model: patch.model ?? settings.model,
|
|
@@ -2195,10 +2897,16 @@ function GitCommitSettingsPanel(props) {
|
|
|
2195
2897
|
if (state.active && state.epoch === epoch) {
|
|
2196
2898
|
setSettings(next);
|
|
2197
2899
|
setNote(true);
|
|
2900
|
+
props.onReady?.();
|
|
2901
|
+
props.onSaved?.(next);
|
|
2902
|
+
}
|
|
2903
|
+
}).catch((reason) => {
|
|
2904
|
+
if (state.active && state.epoch === epoch) {
|
|
2905
|
+
props.onWriteFailed?.();
|
|
2906
|
+
setError(reason instanceof RpcError && reason.code === "stale" ? "stale" : "saveFailed");
|
|
2198
2907
|
}
|
|
2199
|
-
}).catch(() => {
|
|
2200
|
-
if (state.active && state.epoch === epoch) setError("stale");
|
|
2201
2908
|
}).finally(() => {
|
|
2909
|
+
props.onSaving?.(false);
|
|
2202
2910
|
if (!state.active || state.epoch !== epoch) return;
|
|
2203
2911
|
state.writing = false;
|
|
2204
2912
|
setBusy(false);
|
|
@@ -2207,11 +2915,12 @@ function GitCommitSettingsPanel(props) {
|
|
|
2207
2915
|
}, [
|
|
2208
2916
|
call,
|
|
2209
2917
|
settings,
|
|
2210
|
-
text.
|
|
2211
|
-
|
|
2918
|
+
text.stale,
|
|
2919
|
+
props.disabled
|
|
2212
2920
|
]);
|
|
2921
|
+
const locked = busy || controlsLocked;
|
|
2213
2922
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2214
|
-
className: "gcm-root gcm-settings",
|
|
2923
|
+
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2215
2924
|
children: [
|
|
2216
2925
|
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2217
2926
|
role: "alert",
|
|
@@ -2230,7 +2939,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
2230
2939
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2231
2940
|
variant: "ghost",
|
|
2232
2941
|
size: "sm",
|
|
2233
|
-
disabled:
|
|
2942
|
+
disabled: locked,
|
|
2234
2943
|
onClick: () => refreshRef.current(),
|
|
2235
2944
|
children: text.retry
|
|
2236
2945
|
})
|
|
@@ -2244,36 +2953,9 @@ function GitCommitSettingsPanel(props) {
|
|
|
2244
2953
|
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
2245
2954
|
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
2246
2955
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2247
|
-
className: "gcm-root gcm-settings",
|
|
2956
|
+
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2248
2957
|
"data-testid": "git-commit-settings",
|
|
2249
2958
|
children: [
|
|
2250
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2251
|
-
className: "gcm-settings-status",
|
|
2252
|
-
children: [
|
|
2253
|
-
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2254
|
-
role: "status",
|
|
2255
|
-
className: "dsh-ui-hint",
|
|
2256
|
-
children: text.syncUnavailable
|
|
2257
|
-
}),
|
|
2258
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2259
|
-
variant: "ghost",
|
|
2260
|
-
size: "sm",
|
|
2261
|
-
disabled: busy,
|
|
2262
|
-
onClick: () => refreshRef.current(),
|
|
2263
|
-
children: text.refresh
|
|
2264
|
-
}),
|
|
2265
|
-
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2266
|
-
role: "alert",
|
|
2267
|
-
className: "dsh-ui-error",
|
|
2268
|
-
children: text[error]
|
|
2269
|
-
}),
|
|
2270
|
-
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2271
|
-
role: "status",
|
|
2272
|
-
className: "dsh-ui-notice",
|
|
2273
|
-
children: text.saved
|
|
2274
|
-
})
|
|
2275
|
-
]
|
|
2276
|
-
}),
|
|
2277
2959
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2278
2960
|
className: "dsh-ui-field",
|
|
2279
2961
|
children: [
|
|
@@ -2287,13 +2969,13 @@ function GitCommitSettingsPanel(props) {
|
|
|
2287
2969
|
}),
|
|
2288
2970
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
2289
2971
|
locale: language,
|
|
2290
|
-
open: menuOpen && !
|
|
2291
|
-
onOpenChange: (next) => setMenuOpen(next && !
|
|
2292
|
-
listClassName: "gcm-root",
|
|
2972
|
+
open: menuOpen && !locked,
|
|
2973
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2974
|
+
listClassName: "gcm-root gcm-menu-portal",
|
|
2293
2975
|
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2294
2976
|
type: "button",
|
|
2295
2977
|
className: "dsh-model-menu-trigger",
|
|
2296
|
-
disabled:
|
|
2978
|
+
disabled: locked,
|
|
2297
2979
|
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2298
2980
|
children: [
|
|
2299
2981
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2304,7 +2986,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
2304
2986
|
className: "dsh-model-menu-triggerEffort",
|
|
2305
2987
|
children: effortName ?? text.modelDefault
|
|
2306
2988
|
}) : null,
|
|
2307
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !
|
|
2989
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2308
2990
|
]
|
|
2309
2991
|
}),
|
|
2310
2992
|
choices,
|
|
@@ -2345,6 +3027,33 @@ function GitCommitSettingsPanel(props) {
|
|
|
2345
3027
|
className: "dsh-ui-meta",
|
|
2346
3028
|
children: text.modelHint
|
|
2347
3029
|
}),
|
|
3030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3031
|
+
className: "gcm-settings-status",
|
|
3032
|
+
children: [
|
|
3033
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3034
|
+
role: "status",
|
|
3035
|
+
className: "dsh-ui-hint",
|
|
3036
|
+
children: text.syncUnavailable
|
|
3037
|
+
}),
|
|
3038
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3039
|
+
role: "alert",
|
|
3040
|
+
className: "dsh-ui-error",
|
|
3041
|
+
children: text[error]
|
|
3042
|
+
}),
|
|
3043
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3044
|
+
role: "status",
|
|
3045
|
+
className: "dsh-ui-notice",
|
|
3046
|
+
children: text.saved
|
|
3047
|
+
}),
|
|
3048
|
+
error || syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3049
|
+
variant: "ghost",
|
|
3050
|
+
size: "sm",
|
|
3051
|
+
disabled: locked,
|
|
3052
|
+
onClick: () => refreshRef.current(),
|
|
3053
|
+
children: text.refresh
|
|
3054
|
+
}) : null
|
|
3055
|
+
]
|
|
3056
|
+
}),
|
|
2348
3057
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
|
|
2349
3058
|
className: "gcm-root dsh-ui-card",
|
|
2350
3059
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
@@ -2361,7 +3070,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
2361
3070
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
2362
3071
|
checked: settings.allowFallback,
|
|
2363
3072
|
label: text.fallback,
|
|
2364
|
-
disabled:
|
|
3073
|
+
disabled: locked,
|
|
2365
3074
|
onChange: (next) => save({ allowFallback: next })
|
|
2366
3075
|
})]
|
|
2367
3076
|
})
|
|
@@ -2375,6 +3084,7 @@ function apply(ctx) {
|
|
|
2375
3084
|
const settingsSubscriber = createModelSettingsSubscriber(ctx, PLUGIN_NAME);
|
|
2376
3085
|
const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
|
|
2377
3086
|
ctx.provide(GIT_COMMIT_CLIENT_SERVICE, { active: true });
|
|
3087
|
+
ctx.effect(() => registerAuxiliaryModelSettings(ctx), "git-commit.auxiliary-model");
|
|
2378
3088
|
ctx.effect(() => client.locale.register(NS, {
|
|
2379
3089
|
zh: { ...zh },
|
|
2380
3090
|
en: { ...en }
|
|
@@ -2387,19 +3097,16 @@ function apply(ctx) {
|
|
|
2387
3097
|
document.head.appendChild(style);
|
|
2388
3098
|
return () => style.remove();
|
|
2389
3099
|
}, "git-commit.styles");
|
|
2390
|
-
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
2391
|
-
name: "plugins.bundle.config",
|
|
2392
|
-
key: PLUGIN_NAME
|
|
2393
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitCommitSettingsPanel, {
|
|
2394
|
-
client,
|
|
2395
|
-
subscribeSettings
|
|
2396
|
-
}))), "git-commit.settings");
|
|
2397
3100
|
ctx.effect(() => client.slots.inject("conversation.session.header.actions", () => client.slots.register({
|
|
2398
3101
|
name: "conversation.session.header.actions",
|
|
2399
3102
|
id: "git-commit",
|
|
2400
3103
|
order: 10,
|
|
2401
3104
|
locale: NS,
|
|
2402
|
-
inject: () => ({
|
|
3105
|
+
inject: () => ({
|
|
3106
|
+
call: (endpoint, payload) => client.connection.rpc.call(RPC_CHANNEL, endpoint, payload),
|
|
3107
|
+
client,
|
|
3108
|
+
subscribeSettings
|
|
3109
|
+
})
|
|
2403
3110
|
}, GitCommitAction)), "git-commit.slot");
|
|
2404
3111
|
}
|
|
2405
3112
|
//#endregion
|