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