@klarkxy/dsh-current-title 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/docs/README.zh-CN.md +4 -1
- package/lib/client.inner.cjs +1318 -296
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +1318 -296
- package/lib/contracts.d.ts +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +22 -12
- package/lib/index.js.map +1 -1
- package/package.json +17 -12
package/lib/client.js
CHANGED
|
@@ -7,6 +7,7 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
let react_dom = require("react-dom");
|
|
10
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
12
|
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
12
13
|
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
@@ -483,6 +484,29 @@ ${scope} {
|
|
|
483
484
|
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
484
485
|
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
485
486
|
|
|
487
|
+
/* --- Native textarea. The primitives ship no textarea either, so a settings
|
|
488
|
+
* prompt keeps the platform element and the same field shell as the select:
|
|
489
|
+
* 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
|
|
490
|
+
* locking to the select's 34px row. --- */
|
|
491
|
+
& .dsh-ui-textarea {
|
|
492
|
+
box-sizing: border-box;
|
|
493
|
+
width: 100%;
|
|
494
|
+
min-width: 0;
|
|
495
|
+
resize: vertical;
|
|
496
|
+
padding: 8px 12px;
|
|
497
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
498
|
+
border-radius: var(--dsw-radius-md);
|
|
499
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
500
|
+
font: inherit;
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
line-height: 1.5;
|
|
503
|
+
color: var(--dsw-alias-label-primary);
|
|
504
|
+
}
|
|
505
|
+
& .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
506
|
+
& .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
507
|
+
& .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
508
|
+
& .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
509
|
+
|
|
486
510
|
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
487
511
|
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
488
512
|
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
@@ -495,25 +519,18 @@ ${scope} {
|
|
|
495
519
|
}
|
|
496
520
|
//#endregion
|
|
497
521
|
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
498
|
-
/**
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
method: "watch",
|
|
511
|
-
mode: "stream",
|
|
512
|
-
invocation: { kind: "direct" },
|
|
513
|
-
parameters: [],
|
|
514
|
-
cancellation: { parameter: "signal" }
|
|
515
|
-
}]
|
|
516
|
-
};
|
|
522
|
+
/** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
|
|
523
|
+
function modelSettingsChangesIdentity(packageName) {
|
|
524
|
+
const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
|
|
525
|
+
const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
526
|
+
if (!parts.length) throw new Error("Model settings package name has no RPC segment");
|
|
527
|
+
const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
|
|
528
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
|
|
529
|
+
return {
|
|
530
|
+
service: segment,
|
|
531
|
+
namespace: segment
|
|
532
|
+
};
|
|
533
|
+
}
|
|
517
534
|
//#endregion
|
|
518
535
|
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
519
536
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
@@ -528,17 +545,44 @@ function parseModelSettingsChangeFrame(value) {
|
|
|
528
545
|
};
|
|
529
546
|
throw new Error("Invalid model settings change frame");
|
|
530
547
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
548
|
+
/** One owner's contribution. Each package mounts only its own endpoint. */
|
|
549
|
+
function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
|
|
550
|
+
return {
|
|
551
|
+
package: packageName,
|
|
552
|
+
descriptors: [{
|
|
553
|
+
id: `${identity.namespace}/watch`,
|
|
554
|
+
service: identity.service,
|
|
555
|
+
namespace: identity.namespace,
|
|
556
|
+
method: "watch",
|
|
557
|
+
mode: "stream",
|
|
558
|
+
invocation: { kind: "direct" },
|
|
559
|
+
parameters: [],
|
|
560
|
+
cancellation: { parameter: "signal" },
|
|
561
|
+
result: {
|
|
562
|
+
mode: "strict",
|
|
563
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
564
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
565
|
+
}
|
|
566
|
+
}]
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/** Mount from a synchronous apply. Disposal waits for an in-flight mount and
|
|
570
|
+
* then withdraws it, so the contribution cannot outlive the plugin. */
|
|
571
|
+
function mountModelSettingsChanges(ctx, packageName) {
|
|
572
|
+
const contribution = modelSettingsChangesRemote(packageName);
|
|
573
|
+
ctx.inject(["remote"], (injected) => {
|
|
574
|
+
const scope = injected;
|
|
575
|
+
const mounting = scope.remote.$mount(contribution);
|
|
576
|
+
mounting.catch(() => {});
|
|
577
|
+
scope.effect(() => async () => {
|
|
578
|
+
await (await mounting.catch(() => void 0))?.();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
}
|
|
539
582
|
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
540
583
|
* A factory shares one logical stream while at least one subscriber remains. */
|
|
541
|
-
function createModelSettingsSubscriber(ctx,
|
|
584
|
+
function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
|
|
585
|
+
const endpoint = identity;
|
|
542
586
|
const listeners = /* @__PURE__ */ new Set();
|
|
543
587
|
let injection;
|
|
544
588
|
let current;
|
|
@@ -564,12 +608,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
564
608
|
};
|
|
565
609
|
const start = () => {
|
|
566
610
|
const life = ++lifecycle;
|
|
567
|
-
injection = ctx.inject(["remote",
|
|
611
|
+
injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
|
|
568
612
|
if (life !== lifecycle || !listeners.size) return;
|
|
569
613
|
const scope = injected;
|
|
614
|
+
const service = scope.remote[endpoint.namespace];
|
|
615
|
+
if (!service || !("watch" in service) || !service.watch) {
|
|
616
|
+
report("Model settings sync service unavailable");
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const watch = service.watch;
|
|
570
620
|
const stream = scope.remote.$stream({
|
|
571
621
|
name: "Model settings sync",
|
|
572
|
-
open: (signal) =>
|
|
622
|
+
open: (signal) => watch(signal),
|
|
573
623
|
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
574
624
|
});
|
|
575
625
|
const session = {
|
|
@@ -604,7 +654,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
604
654
|
invalidate();
|
|
605
655
|
} else {
|
|
606
656
|
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
607
|
-
if (
|
|
657
|
+
if (accept(frame.ns)) invalidate();
|
|
608
658
|
}
|
|
609
659
|
}
|
|
610
660
|
} catch (error) {
|
|
@@ -650,6 +700,965 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
650
700
|
} });
|
|
651
701
|
}
|
|
652
702
|
//#endregion
|
|
703
|
+
//#region ../dsh-model-route/src/catalog.ts
|
|
704
|
+
/** Both services are required: Cordis checks the nested namespace separately. */
|
|
705
|
+
const MODEL_CATALOG_INJECT = ["remote", "remote.session"];
|
|
706
|
+
function asRecord(value) {
|
|
707
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
708
|
+
}
|
|
709
|
+
function asText(value) {
|
|
710
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
711
|
+
}
|
|
712
|
+
/** A useful diagnostic even when a transport rejects with a plain object. */
|
|
713
|
+
function modelCatalogErrorMessage(value) {
|
|
714
|
+
if (value instanceof Error && value.message) return value.message;
|
|
715
|
+
return asText(asRecord(value)?.message) ?? asText(value) ?? "Unable to load the model catalog.";
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Validate and unwrap a successful catalog without discarding provider failures
|
|
719
|
+
* or newer host metadata. Names and metadata remain optional for older hosts.
|
|
720
|
+
* A broken response is never presented as a successfully loaded empty menu.
|
|
721
|
+
*/
|
|
722
|
+
function checkedModelCatalog(value) {
|
|
723
|
+
const envelope = asRecord(value);
|
|
724
|
+
if (envelope?.ok === false) throw new Error(modelCatalogErrorMessage(envelope.error ?? envelope));
|
|
725
|
+
const root = envelope?.ok === true ? asRecord(envelope.value) : envelope;
|
|
726
|
+
const malformed = (field) => {
|
|
727
|
+
throw new Error(`Invalid model catalog: ${field}.`);
|
|
728
|
+
};
|
|
729
|
+
if (!root || !Array.isArray(root.groups)) return malformed("groups must be an array");
|
|
730
|
+
for (const value of root.groups) {
|
|
731
|
+
const group = asRecord(value);
|
|
732
|
+
if (!group || !asText(group.id) || !Array.isArray(group.models)) return malformed("each group needs an id and models array");
|
|
733
|
+
for (const value of group.models) {
|
|
734
|
+
const model = asRecord(value);
|
|
735
|
+
if (!model || !asText(model.id)) return malformed("each model needs an id");
|
|
736
|
+
if (model.reasoning !== void 0) {
|
|
737
|
+
const reasoning = asRecord(model.reasoning);
|
|
738
|
+
if (!reasoning || !Array.isArray(reasoning.efforts) || reasoning.efforts.some((value) => !parseEffort(value))) return malformed("reasoning efforts must be an array of identified efforts");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (root.failures !== void 0 && (!Array.isArray(root.failures) || root.failures.some((value) => {
|
|
743
|
+
const failure = asRecord(value);
|
|
744
|
+
return !failure || !asText(failure.id) || typeof failure.name !== "string" || typeof failure.message !== "string";
|
|
745
|
+
}))) return malformed("failures must be an array of provider failures");
|
|
746
|
+
return root;
|
|
747
|
+
}
|
|
748
|
+
/** Read through the injected session namespace; absence and rejection are errors. */
|
|
749
|
+
async function readModelCatalog(client) {
|
|
750
|
+
const session = client.remote?.session;
|
|
751
|
+
if (typeof session?.modelCatalog !== "function") throw new Error("Model catalog unavailable: inject both remote and remote.session.");
|
|
752
|
+
return checkedModelCatalog(await session.modelCatalog());
|
|
753
|
+
}
|
|
754
|
+
/** Provider failures are partial results, not a transport failure. */
|
|
755
|
+
function modelCatalogFailures(value) {
|
|
756
|
+
try {
|
|
757
|
+
return checkedModelCatalog(value).failures ?? [];
|
|
758
|
+
} catch {
|
|
759
|
+
return [];
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function parseEffort(value) {
|
|
763
|
+
const row = asRecord(value);
|
|
764
|
+
const id = row ? asText(row.id) : void 0;
|
|
765
|
+
if (!id) return void 0;
|
|
766
|
+
return {
|
|
767
|
+
id,
|
|
768
|
+
name: row ? asText(row.name) ?? id : id
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
/** Parse the host session catalog into flat choices, keeping a saved route selectable. */
|
|
772
|
+
function parseModelMenuChoices(value, bound) {
|
|
773
|
+
const envelope = asRecord(value);
|
|
774
|
+
const root = (envelope && envelope.ok === true ? asRecord(envelope.value) : void 0) ?? envelope ?? {};
|
|
775
|
+
const groups = Array.isArray(root.groups) ? root.groups : [];
|
|
776
|
+
const choices = [];
|
|
777
|
+
for (const group of groups) {
|
|
778
|
+
const groupRow = asRecord(group);
|
|
779
|
+
const provider = groupRow ? asText(groupRow.id) : void 0;
|
|
780
|
+
if (!provider || !groupRow || !Array.isArray(groupRow.models)) continue;
|
|
781
|
+
const providerName = asText(groupRow.name) ?? provider;
|
|
782
|
+
for (const entry of groupRow.models) {
|
|
783
|
+
const modelRow = asRecord(entry);
|
|
784
|
+
const model = modelRow ? asText(modelRow.id) : void 0;
|
|
785
|
+
if (!model || !modelRow) continue;
|
|
786
|
+
const name = asText(modelRow.name) ?? model;
|
|
787
|
+
const reasoning = asRecord(modelRow.reasoning);
|
|
788
|
+
const efforts = reasoning && Array.isArray(reasoning.efforts) ? reasoning.efforts.flatMap((effort) => {
|
|
789
|
+
const parsed = parseEffort(effort);
|
|
790
|
+
return parsed ? [parsed] : [];
|
|
791
|
+
}) : [];
|
|
792
|
+
choices.push({
|
|
793
|
+
provider,
|
|
794
|
+
model,
|
|
795
|
+
label: `${providerName} / ${name}`,
|
|
796
|
+
efforts
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (bound?.provider && bound.model && !choices.some((item) => item.provider === bound.provider && item.model === bound.model)) choices.push({
|
|
801
|
+
provider: bound.provider,
|
|
802
|
+
model: bound.model,
|
|
803
|
+
label: `${bound.provider} / ${bound.model}`,
|
|
804
|
+
efforts: []
|
|
805
|
+
});
|
|
806
|
+
return choices;
|
|
807
|
+
}
|
|
808
|
+
/** Reasoning-effort options for one choice; the saved value stays selectable. */
|
|
809
|
+
function modelMenuEffortOptions(choice, current) {
|
|
810
|
+
const listed = (choice?.efforts ?? []).map((item) => ({ ...item }));
|
|
811
|
+
if (current && !listed.some((item) => item.id === current)) listed.push({
|
|
812
|
+
id: current,
|
|
813
|
+
name: current
|
|
814
|
+
});
|
|
815
|
+
return listed;
|
|
816
|
+
}
|
|
817
|
+
//#endregion
|
|
818
|
+
//#region ../dsh-model-route/src/menu.ts
|
|
819
|
+
/** Localize standard effort ids only when the provider has not supplied a display name. */
|
|
820
|
+
function modelMenuEffortLabel(effort, locale) {
|
|
821
|
+
if (effort.name !== effort.id) return effort.name;
|
|
822
|
+
return {
|
|
823
|
+
off: ["关闭", "Off"],
|
|
824
|
+
none: ["无", "None"],
|
|
825
|
+
minimal: ["最低", "Minimal"],
|
|
826
|
+
low: ["低", "Low"],
|
|
827
|
+
medium: ["中", "Medium"],
|
|
828
|
+
high: ["高", "High"],
|
|
829
|
+
xhigh: ["极高", "Extra high"],
|
|
830
|
+
max: ["最高", "Maximum"],
|
|
831
|
+
ultra: ["超高", "Ultra"]
|
|
832
|
+
}[effort.id]?.[locale?.toLowerCase().startsWith("zh") ? 0 : 1] ?? effort.name;
|
|
833
|
+
}
|
|
834
|
+
/** The short model label shown inside a provider group. */
|
|
835
|
+
function modelMenuShortName(choice) {
|
|
836
|
+
const sep = choice.label.indexOf(" / ");
|
|
837
|
+
return sep > 0 ? choice.label.slice(sep + 3) : choice.model;
|
|
838
|
+
}
|
|
839
|
+
/** Group catalog choices by provider and filter them by a case-insensitive query. */
|
|
840
|
+
function groupModelMenuChoices(choices, query) {
|
|
841
|
+
const needle = query.trim().toLowerCase();
|
|
842
|
+
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
|
|
843
|
+
const groups = [];
|
|
844
|
+
for (const choice of matched) {
|
|
845
|
+
const sep = choice.label.indexOf(" / ");
|
|
846
|
+
const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
|
|
847
|
+
const last = groups.at(-1);
|
|
848
|
+
if (last && last.provider === choice.provider) last.items.push(choice);
|
|
849
|
+
else groups.push({
|
|
850
|
+
provider: choice.provider,
|
|
851
|
+
name,
|
|
852
|
+
items: [choice]
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
return groups;
|
|
856
|
+
}
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region ../dsh-model-route/src/catalog-ui.tsx
|
|
859
|
+
/**
|
|
860
|
+
* Latest refresh wins, including retries while a previous request is pending.
|
|
861
|
+
* Keep load stable (for example with useCallback); changing it invalidates the
|
|
862
|
+
* previous source and fetches again, so switching clients cannot publish stale data.
|
|
863
|
+
*/
|
|
864
|
+
function useModelCatalog(load, options = {}) {
|
|
865
|
+
const enabled = options.enabled !== false;
|
|
866
|
+
const enabledRef = (0, react.useRef)(enabled);
|
|
867
|
+
enabledRef.current = enabled;
|
|
868
|
+
const live = (0, react.useRef)(false);
|
|
869
|
+
const source = (0, react.useRef)(load);
|
|
870
|
+
const revision = (0, react.useRef)(0);
|
|
871
|
+
const [state, setState] = (0, react.useState)(() => {
|
|
872
|
+
try {
|
|
873
|
+
return {
|
|
874
|
+
catalog: options.initialCatalog === void 0 ? void 0 : checkedModelCatalog(options.initialCatalog),
|
|
875
|
+
loading: enabled,
|
|
876
|
+
error: ""
|
|
877
|
+
};
|
|
878
|
+
} catch (error) {
|
|
879
|
+
return {
|
|
880
|
+
catalog: void 0,
|
|
881
|
+
loading: enabled,
|
|
882
|
+
error: modelCatalogErrorMessage(error)
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
887
|
+
if (!live.current || !enabledRef.current) return;
|
|
888
|
+
const request = ++revision.current;
|
|
889
|
+
setState((previous) => ({
|
|
890
|
+
...previous,
|
|
891
|
+
loading: true,
|
|
892
|
+
error: ""
|
|
893
|
+
}));
|
|
894
|
+
try {
|
|
895
|
+
const catalog = checkedModelCatalog(await load());
|
|
896
|
+
if (live.current && request === revision.current) setState({
|
|
897
|
+
catalog,
|
|
898
|
+
loading: false,
|
|
899
|
+
error: ""
|
|
900
|
+
});
|
|
901
|
+
} catch (error) {
|
|
902
|
+
if (live.current && request === revision.current) setState((previous) => ({
|
|
903
|
+
...previous,
|
|
904
|
+
loading: false,
|
|
905
|
+
error: modelCatalogErrorMessage(error)
|
|
906
|
+
}));
|
|
907
|
+
}
|
|
908
|
+
}, [load, enabled]);
|
|
909
|
+
(0, react.useEffect)(() => {
|
|
910
|
+
live.current = true;
|
|
911
|
+
if (source.current !== load) {
|
|
912
|
+
source.current = load;
|
|
913
|
+
setState({
|
|
914
|
+
catalog: void 0,
|
|
915
|
+
loading: enabled,
|
|
916
|
+
error: ""
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
if (enabled) refresh();
|
|
920
|
+
else setState((previous) => ({
|
|
921
|
+
...previous,
|
|
922
|
+
loading: false
|
|
923
|
+
}));
|
|
924
|
+
return () => {
|
|
925
|
+
live.current = false;
|
|
926
|
+
++revision.current;
|
|
927
|
+
};
|
|
928
|
+
}, [enabled, refresh]);
|
|
929
|
+
(0, react.useEffect)(() => {
|
|
930
|
+
if (!enabled) return;
|
|
931
|
+
const reload = () => {
|
|
932
|
+
refresh();
|
|
933
|
+
};
|
|
934
|
+
if (typeof window !== "undefined") window.addEventListener("focus", reload);
|
|
935
|
+
let unsubscribe;
|
|
936
|
+
try {
|
|
937
|
+
unsubscribe = options.subscribe?.(reload);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
setState((previous) => ({
|
|
940
|
+
...previous,
|
|
941
|
+
error: modelCatalogErrorMessage(error)
|
|
942
|
+
}));
|
|
943
|
+
}
|
|
944
|
+
return () => {
|
|
945
|
+
if (typeof window !== "undefined") window.removeEventListener("focus", reload);
|
|
946
|
+
unsubscribe?.();
|
|
947
|
+
};
|
|
948
|
+
}, [
|
|
949
|
+
enabled,
|
|
950
|
+
refresh,
|
|
951
|
+
options.subscribe
|
|
952
|
+
]);
|
|
953
|
+
const choices = (0, react.useMemo)(() => parseModelMenuChoices(state.catalog), [state.catalog]);
|
|
954
|
+
const failures = (0, react.useMemo)(() => state.catalog === void 0 ? [] : modelCatalogFailures(state.catalog), [state.catalog]);
|
|
955
|
+
return {
|
|
956
|
+
...state,
|
|
957
|
+
choices,
|
|
958
|
+
failures,
|
|
959
|
+
refresh
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
/** Also accepts a mapped state from a caller that owns broader async orchestration. */
|
|
963
|
+
function ModelCatalogStatus({ state, locale }) {
|
|
964
|
+
const zh = locale?.toLowerCase().startsWith("zh") === true;
|
|
965
|
+
const copy = zh ? {
|
|
966
|
+
loading: "正在加载模型…",
|
|
967
|
+
error: "模型列表加载失败",
|
|
968
|
+
partial: "部分提供商的模型未能加载",
|
|
969
|
+
retry: "重试"
|
|
970
|
+
} : {
|
|
971
|
+
loading: "Loading models…",
|
|
972
|
+
error: "Unable to load models",
|
|
973
|
+
partial: "Some providers could not load their models",
|
|
974
|
+
retry: "Retry"
|
|
975
|
+
};
|
|
976
|
+
if (!state.loading && !state.error && state.failures.length === 0) return null;
|
|
977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
978
|
+
className: "dsh-model-catalog-status",
|
|
979
|
+
lang: zh ? "zh" : "en",
|
|
980
|
+
children: [
|
|
981
|
+
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
982
|
+
role: "status",
|
|
983
|
+
children: copy.loading
|
|
984
|
+
}) : null,
|
|
985
|
+
state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
986
|
+
role: "alert",
|
|
987
|
+
children: [
|
|
988
|
+
copy.error,
|
|
989
|
+
": ",
|
|
990
|
+
state.error
|
|
991
|
+
]
|
|
992
|
+
}) : null,
|
|
993
|
+
state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
994
|
+
role: "status",
|
|
995
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
996
|
+
failure.name || failure.id,
|
|
997
|
+
": ",
|
|
998
|
+
failure.message
|
|
999
|
+
] }, `${failure.id}:${index}`)) })]
|
|
1000
|
+
}) : null,
|
|
1001
|
+
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1002
|
+
type: "button",
|
|
1003
|
+
size: "sm",
|
|
1004
|
+
disabled: state.loading,
|
|
1005
|
+
onClick: () => {
|
|
1006
|
+
state.refresh();
|
|
1007
|
+
},
|
|
1008
|
+
children: copy.retry
|
|
1009
|
+
}) : null
|
|
1010
|
+
]
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
//#endregion
|
|
1014
|
+
//#region ../dsh-model-route/src/ui.tsx
|
|
1015
|
+
/**
|
|
1016
|
+
* ModelMenu: the standard editor of one model-route field — the visible half
|
|
1017
|
+
* of the x-model-route contract. A feature plugin's settings page renders it
|
|
1018
|
+
* for its own marked field; the model hub renders one per detected field.
|
|
1019
|
+
*
|
|
1020
|
+
* It is a plugin-side copy of the host's composer model picker
|
|
1021
|
+
* (dsh-client-ui-model-selection's ModelSelect), rebuilt from the official
|
|
1022
|
+
* primitives and the published bundle's behavior: one card that opens on the
|
|
1023
|
+
* root pane (Model / Effort cells), drills into the provider-grouped,
|
|
1024
|
+
* searchable model list or the effort list, and Escapes back before closing.
|
|
1025
|
+
* Sticky group headings, subsequence ranking, highlight walking and focus
|
|
1026
|
+
* hand-back all match the host. SYNC POINT: when the host's ModelSelect or
|
|
1027
|
+
* ModelSelect.module.css changes, this copy must be re-transcribed; styles
|
|
1028
|
+
* below may only reference --dsw-* theme tokens, never literal colors.
|
|
1029
|
+
*
|
|
1030
|
+
* It statically imports the primitives, so only browser clients should load
|
|
1031
|
+
* this entry; host code and node tests use the core entry (`./index.ts`).
|
|
1032
|
+
*/
|
|
1033
|
+
/** Unplaced portal card: hidden but laid out at a fixed origin so measurements are real. */
|
|
1034
|
+
const MEASURE_STYLE = {
|
|
1035
|
+
visibility: "hidden",
|
|
1036
|
+
left: 0,
|
|
1037
|
+
top: 0
|
|
1038
|
+
};
|
|
1039
|
+
function ModelMenu(props) {
|
|
1040
|
+
const language = props.locale?.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
1041
|
+
const copy = language === "zh" ? {
|
|
1042
|
+
model: "模型",
|
|
1043
|
+
effort: "思考强度",
|
|
1044
|
+
default: "默认",
|
|
1045
|
+
search: "搜索模型…",
|
|
1046
|
+
empty: "没有匹配的模型",
|
|
1047
|
+
clear: "清除搜索"
|
|
1048
|
+
} : {
|
|
1049
|
+
model: "Model",
|
|
1050
|
+
effort: "Reasoning",
|
|
1051
|
+
default: "Default",
|
|
1052
|
+
search: "Search models…",
|
|
1053
|
+
empty: "No matching models",
|
|
1054
|
+
clear: "Clear search"
|
|
1055
|
+
};
|
|
1056
|
+
const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, className, listClassName } = props;
|
|
1057
|
+
const rootRef = (0, react.useRef)(null);
|
|
1058
|
+
const openRef = (0, react.useRef)(open);
|
|
1059
|
+
openRef.current = open;
|
|
1060
|
+
const pendingOpenRef = (0, react.useRef)(false);
|
|
1061
|
+
const pendingRestoreFocus = (0, react.useRef)(false);
|
|
1062
|
+
const [requestedOpen, setRequestedOpen] = (0, react.useState)(false);
|
|
1063
|
+
const menuRef = (0, react.useRef)(null);
|
|
1064
|
+
const groupsRef = (0, react.useRef)(null);
|
|
1065
|
+
const searchRef = (0, react.useRef)(null);
|
|
1066
|
+
const itemRefs = (0, react.useRef)([]);
|
|
1067
|
+
const paneFocus = (0, react.useRef)(null);
|
|
1068
|
+
const [pane, setPane] = (0, react.useState)("root");
|
|
1069
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
1070
|
+
const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
|
|
1071
|
+
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
1072
|
+
const id = (0, react.useId)();
|
|
1073
|
+
const showSearch = choices.length > 4;
|
|
1074
|
+
const effortRows = (0, react.useMemo)(() => [{
|
|
1075
|
+
id: "",
|
|
1076
|
+
name: defaultEffortLabel
|
|
1077
|
+
}, ...efforts.map((effort) => ({
|
|
1078
|
+
...effort,
|
|
1079
|
+
name: modelMenuEffortLabel(effort, language)
|
|
1080
|
+
}))], [
|
|
1081
|
+
defaultEffortLabel,
|
|
1082
|
+
efforts,
|
|
1083
|
+
language
|
|
1084
|
+
]);
|
|
1085
|
+
const filteredGroups = (0, react.useMemo)(() => groupModelMenuChoices(choices, "").map((group) => ({
|
|
1086
|
+
...group,
|
|
1087
|
+
items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
|
|
1088
|
+
choice,
|
|
1089
|
+
name: modelMenuShortName(choice),
|
|
1090
|
+
label: choice.model
|
|
1091
|
+
})), showSearch ? query.trim() : "")
|
|
1092
|
+
})).filter((group) => group.items.length > 0), [
|
|
1093
|
+
choices,
|
|
1094
|
+
query,
|
|
1095
|
+
showSearch
|
|
1096
|
+
]);
|
|
1097
|
+
const visibleModels = (0, react.useMemo)(() => filteredGroups.flatMap((group) => group.items.map((item) => item.choice)), [filteredGroups]);
|
|
1098
|
+
const currentVisibleIndex = visibleModels.findIndex((choice) => choice.provider === selected?.provider && choice.model === selected.model);
|
|
1099
|
+
const activeModelIndex = Math.min(highlightedIndex ?? Math.max(0, currentVisibleIndex), visibleModels.length - 1);
|
|
1100
|
+
const changeOpen = (next) => {
|
|
1101
|
+
if (openRef.current === next) return;
|
|
1102
|
+
openRef.current = next;
|
|
1103
|
+
onOpenChange(next);
|
|
1104
|
+
};
|
|
1105
|
+
const requestOpen = () => {
|
|
1106
|
+
if (open || pendingOpenRef.current) return;
|
|
1107
|
+
pendingOpenRef.current = true;
|
|
1108
|
+
pendingRestoreFocus.current = false;
|
|
1109
|
+
setRequestedOpen(true);
|
|
1110
|
+
};
|
|
1111
|
+
const close = (restoreFocus = false) => {
|
|
1112
|
+
const active = document.activeElement;
|
|
1113
|
+
pendingRestoreFocus.current = restoreFocus && (active === document.body || rootRef.current?.contains(active) === true || menuRef.current?.contains(active) === true);
|
|
1114
|
+
changeOpen(false);
|
|
1115
|
+
};
|
|
1116
|
+
const drill = (next) => {
|
|
1117
|
+
setQuery("");
|
|
1118
|
+
setHighlightedIndex(null);
|
|
1119
|
+
paneFocus.current = "drill";
|
|
1120
|
+
setPane(next);
|
|
1121
|
+
};
|
|
1122
|
+
/** Leave a drilled pane for the root one, handing the keyboard back to its cell. */
|
|
1123
|
+
const back = (from) => {
|
|
1124
|
+
paneFocus.current = from;
|
|
1125
|
+
setPane("root");
|
|
1126
|
+
};
|
|
1127
|
+
const moveFocus = (offset) => {
|
|
1128
|
+
const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
|
|
1129
|
+
if (!items.length) return;
|
|
1130
|
+
const active = items.findIndex((item) => item === document.activeElement);
|
|
1131
|
+
items[active === -1 ? offset > 0 ? 0 : items.length - 1 : (active + offset + items.length) % items.length]?.focus();
|
|
1132
|
+
};
|
|
1133
|
+
(0, react.useLayoutEffect)(() => {
|
|
1134
|
+
if (open || !pendingRestoreFocus.current) return;
|
|
1135
|
+
const active = document.activeElement;
|
|
1136
|
+
if (active !== document.body && !rootRef.current?.contains(active)) {
|
|
1137
|
+
pendingRestoreFocus.current = false;
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const trigger = rootRef.current?.querySelector("button");
|
|
1141
|
+
if (!trigger || trigger.disabled) return;
|
|
1142
|
+
trigger.focus();
|
|
1143
|
+
if (document.activeElement === trigger) pendingRestoreFocus.current = false;
|
|
1144
|
+
});
|
|
1145
|
+
(0, react.useEffect)(() => {
|
|
1146
|
+
if (open || !pendingRestoreFocus.current) return;
|
|
1147
|
+
const cancelOutside = (event) => {
|
|
1148
|
+
if (event.target instanceof Node && !rootRef.current?.contains(event.target)) pendingRestoreFocus.current = false;
|
|
1149
|
+
};
|
|
1150
|
+
document.addEventListener("focusin", cancelOutside);
|
|
1151
|
+
document.addEventListener("pointerdown", cancelOutside);
|
|
1152
|
+
return () => {
|
|
1153
|
+
document.removeEventListener("focusin", cancelOutside);
|
|
1154
|
+
document.removeEventListener("pointerdown", cancelOutside);
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1157
|
+
(0, react.useEffect)(() => {
|
|
1158
|
+
if (!requestedOpen) return;
|
|
1159
|
+
pendingOpenRef.current = false;
|
|
1160
|
+
setRequestedOpen(false);
|
|
1161
|
+
if (!open) changeOpen(true);
|
|
1162
|
+
}, [requestedOpen, open]);
|
|
1163
|
+
(0, react.useEffect)(() => {
|
|
1164
|
+
if (!open) return;
|
|
1165
|
+
setPane("root");
|
|
1166
|
+
setQuery("");
|
|
1167
|
+
setHighlightedIndex(null);
|
|
1168
|
+
}, [open]);
|
|
1169
|
+
(0, react.useEffect)(() => {
|
|
1170
|
+
if (!open) return;
|
|
1171
|
+
const closeOutside = (event) => {
|
|
1172
|
+
const target = event.target;
|
|
1173
|
+
if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
1174
|
+
changeOpen(false);
|
|
1175
|
+
};
|
|
1176
|
+
document.addEventListener("mousedown", closeOutside);
|
|
1177
|
+
return () => document.removeEventListener("mousedown", closeOutside);
|
|
1178
|
+
}, [open, onOpenChange]);
|
|
1179
|
+
(0, react.useEffect)(() => {
|
|
1180
|
+
const intent = paneFocus.current;
|
|
1181
|
+
if (!open || menuPos === null || intent === null) return;
|
|
1182
|
+
if (intent !== "drill" && pane !== "root") return;
|
|
1183
|
+
paneFocus.current = null;
|
|
1184
|
+
if (intent === "drill") {
|
|
1185
|
+
if (pane === "model" && showSearch) {
|
|
1186
|
+
searchRef.current?.focus();
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
(menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? itemRefs.current.find((item) => item !== null && !item.disabled))?.focus();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
itemRefs.current[intent === "effort" ? 1 : 0]?.focus();
|
|
1193
|
+
}, [
|
|
1194
|
+
open,
|
|
1195
|
+
pane,
|
|
1196
|
+
showSearch,
|
|
1197
|
+
menuPos
|
|
1198
|
+
]);
|
|
1199
|
+
(0, react.useEffect)(() => {
|
|
1200
|
+
const viewport = groupsRef.current;
|
|
1201
|
+
if (viewport === null) return;
|
|
1202
|
+
return (0, _deepseek_ai_dsh_client_ui_primitives.observeStickyMenuGroups)(viewport);
|
|
1203
|
+
}, [
|
|
1204
|
+
open,
|
|
1205
|
+
pane,
|
|
1206
|
+
filteredGroups
|
|
1207
|
+
]);
|
|
1208
|
+
(0, react.useLayoutEffect)(() => {
|
|
1209
|
+
if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
|
|
1210
|
+
}, [
|
|
1211
|
+
open,
|
|
1212
|
+
pane,
|
|
1213
|
+
activeModelIndex,
|
|
1214
|
+
visibleModels,
|
|
1215
|
+
leading.length
|
|
1216
|
+
]);
|
|
1217
|
+
(0, react.useLayoutEffect)(() => {
|
|
1218
|
+
if (!open) {
|
|
1219
|
+
setMenuPos(null);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const place = () => {
|
|
1223
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
1224
|
+
if (!rect) return;
|
|
1225
|
+
const margin = 12, lw = menuRef.current?.offsetWidth ?? 0, lh = menuRef.current?.offsetHeight ?? 0;
|
|
1226
|
+
let x = rect.right - lw, y = rect.top - 8 - lh;
|
|
1227
|
+
if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
|
|
1228
|
+
if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
|
|
1229
|
+
setMenuPos({
|
|
1230
|
+
left: x,
|
|
1231
|
+
top: y
|
|
1232
|
+
});
|
|
1233
|
+
};
|
|
1234
|
+
place();
|
|
1235
|
+
window.addEventListener("scroll", place, true);
|
|
1236
|
+
window.addEventListener("resize", place);
|
|
1237
|
+
return () => {
|
|
1238
|
+
window.removeEventListener("scroll", place, true);
|
|
1239
|
+
window.removeEventListener("resize", place);
|
|
1240
|
+
};
|
|
1241
|
+
}, [
|
|
1242
|
+
open,
|
|
1243
|
+
pane,
|
|
1244
|
+
choices,
|
|
1245
|
+
query
|
|
1246
|
+
]);
|
|
1247
|
+
function onKeyDown(event) {
|
|
1248
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
1249
|
+
if (!open && [
|
|
1250
|
+
"Enter",
|
|
1251
|
+
" ",
|
|
1252
|
+
"ArrowDown",
|
|
1253
|
+
"ArrowUp"
|
|
1254
|
+
].includes(event.key)) {
|
|
1255
|
+
const button = event.target instanceof Element ? event.target.closest("button") : null;
|
|
1256
|
+
if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
|
|
1257
|
+
event.preventDefault();
|
|
1258
|
+
paneFocus.current = "model";
|
|
1259
|
+
requestOpen();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (event.key === "Escape" && open) {
|
|
1263
|
+
event.preventDefault();
|
|
1264
|
+
if (pane !== "root") back(pane);
|
|
1265
|
+
else close(true);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (!open) return;
|
|
1269
|
+
if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1270
|
+
event.preventDefault();
|
|
1271
|
+
if (visibleModels.length > 0) {
|
|
1272
|
+
setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
|
|
1273
|
+
searchRef.current?.focus();
|
|
1274
|
+
}
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (pane === "model" && showSearch && event.target instanceof HTMLInputElement && (event.key === "Enter" || event.key === "Tab" && !event.shiftKey)) {
|
|
1278
|
+
if (event.key === "Tab" && visibleModels.length === 0) return;
|
|
1279
|
+
event.preventDefault();
|
|
1280
|
+
const highlighted = visibleModels[activeModelIndex];
|
|
1281
|
+
if (highlighted) {
|
|
1282
|
+
onPick({
|
|
1283
|
+
provider: highlighted.provider,
|
|
1284
|
+
model: highlighted.model
|
|
1285
|
+
});
|
|
1286
|
+
close(true);
|
|
1287
|
+
}
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if (event.key === "Tab") {
|
|
1291
|
+
if (event.shiftKey) {
|
|
1292
|
+
event.preventDefault();
|
|
1293
|
+
if (pane !== "root") back(pane);
|
|
1294
|
+
else close(true);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const focused = document.activeElement;
|
|
1298
|
+
const rows = itemRefs.current.filter((item) => item !== null);
|
|
1299
|
+
if (focused instanceof HTMLButtonElement && rows.includes(focused)) {
|
|
1300
|
+
event.preventDefault();
|
|
1301
|
+
focused.click();
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (pane === "model" && showSearch) {
|
|
1305
|
+
event.preventDefault();
|
|
1306
|
+
setHighlightedIndex(null);
|
|
1307
|
+
searchRef.current?.focus();
|
|
1308
|
+
}
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1312
|
+
event.preventDefault();
|
|
1313
|
+
moveFocus(event.key === "ArrowDown" ? 1 : -1);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function onBlur(event) {
|
|
1317
|
+
if (event.relatedTarget instanceof Node && (rootRef.current?.contains(event.relatedTarget) || menuRef.current?.contains(event.relatedTarget))) return;
|
|
1318
|
+
if (open) changeOpen(false);
|
|
1319
|
+
}
|
|
1320
|
+
itemRefs.current = [];
|
|
1321
|
+
let itemIndex = 0;
|
|
1322
|
+
let modelIndex = 0;
|
|
1323
|
+
const itemRef = () => {
|
|
1324
|
+
const at = itemIndex++;
|
|
1325
|
+
return (node) => {
|
|
1326
|
+
itemRefs.current[at] = node;
|
|
1327
|
+
};
|
|
1328
|
+
};
|
|
1329
|
+
const menu = open && typeof document !== "undefined" ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
|
|
1330
|
+
ref: menuRef,
|
|
1331
|
+
id: `${id}-menu`,
|
|
1332
|
+
className: `dsh-model-menu-menu${listClassName ? ` ${listClassName}` : ""}`,
|
|
1333
|
+
style: menuPos ?? MEASURE_STYLE,
|
|
1334
|
+
role: pane === "model" ? "group" : "menu",
|
|
1335
|
+
"aria-label": modelLabel,
|
|
1336
|
+
lang: language,
|
|
1337
|
+
children: [
|
|
1338
|
+
pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1339
|
+
ref: itemRef(),
|
|
1340
|
+
type: "button",
|
|
1341
|
+
role: "menuitem",
|
|
1342
|
+
className: "dsh-model-menu-cell",
|
|
1343
|
+
onClick: () => drill("model"),
|
|
1344
|
+
children: [
|
|
1345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1346
|
+
className: "dsh-model-menu-cellLabel",
|
|
1347
|
+
children: modelLabel
|
|
1348
|
+
}),
|
|
1349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1350
|
+
className: "dsh-model-menu-cellValue",
|
|
1351
|
+
children: modelValue
|
|
1352
|
+
}),
|
|
1353
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
|
|
1354
|
+
]
|
|
1355
|
+
}), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1356
|
+
ref: itemRef(),
|
|
1357
|
+
type: "button",
|
|
1358
|
+
role: "menuitem",
|
|
1359
|
+
className: "dsh-model-menu-cell",
|
|
1360
|
+
onClick: () => drill("effort"),
|
|
1361
|
+
children: [
|
|
1362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1363
|
+
className: "dsh-model-menu-cellLabel",
|
|
1364
|
+
children: effortLabel
|
|
1365
|
+
}),
|
|
1366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1367
|
+
className: "dsh-model-menu-cellValue",
|
|
1368
|
+
children: effortValue ?? effortRows.find((row) => row.id === (selectedEffort ?? ""))?.name ?? defaultEffortLabel
|
|
1369
|
+
}),
|
|
1370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
|
|
1371
|
+
]
|
|
1372
|
+
}) : null] }) : null,
|
|
1373
|
+
pane === "model" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1374
|
+
showSearch ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1375
|
+
className: "dsh-model-menu-searchRow",
|
|
1376
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1377
|
+
ref: searchRef,
|
|
1378
|
+
className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
|
|
1379
|
+
type: "text",
|
|
1380
|
+
role: "searchbox",
|
|
1381
|
+
"aria-label": searchPlaceholder,
|
|
1382
|
+
"aria-controls": `${id}-models`,
|
|
1383
|
+
"aria-activedescendant": activeModelIndex < 0 ? void 0 : `${id}-model-${activeModelIndex}`,
|
|
1384
|
+
placeholder: searchPlaceholder,
|
|
1385
|
+
value: query,
|
|
1386
|
+
onChange: (event) => {
|
|
1387
|
+
setQuery(event.target.value);
|
|
1388
|
+
setHighlightedIndex(0);
|
|
1389
|
+
}
|
|
1390
|
+
}), query !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1391
|
+
type: "button",
|
|
1392
|
+
className: "dsh-model-menu-searchClear",
|
|
1393
|
+
"aria-label": clearSearchLabel,
|
|
1394
|
+
onClick: () => {
|
|
1395
|
+
setQuery("");
|
|
1396
|
+
setHighlightedIndex(null);
|
|
1397
|
+
searchRef.current?.focus();
|
|
1398
|
+
},
|
|
1399
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, {})
|
|
1400
|
+
}) : null]
|
|
1401
|
+
}) : null,
|
|
1402
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1403
|
+
ref: groupsRef,
|
|
1404
|
+
id: `${id}-models`,
|
|
1405
|
+
className: "dsh-model-menu-groups scrollable",
|
|
1406
|
+
role: "menu",
|
|
1407
|
+
"aria-label": modelLabel,
|
|
1408
|
+
hidden: filteredGroups.length === 0,
|
|
1409
|
+
children: [leading.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1410
|
+
className: "dsh-model-menu-leading",
|
|
1411
|
+
children: leading.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1412
|
+
ref: itemRef(),
|
|
1413
|
+
type: "button",
|
|
1414
|
+
role: "menuitemradio",
|
|
1415
|
+
"aria-checked": item.selected === true,
|
|
1416
|
+
className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
|
|
1417
|
+
onClick: () => {
|
|
1418
|
+
onPickLeading?.(item.id);
|
|
1419
|
+
close(true);
|
|
1420
|
+
},
|
|
1421
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1422
|
+
className: "dsh-model-menu-optionCopy",
|
|
1423
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1424
|
+
className: "dsh-model-menu-modelName",
|
|
1425
|
+
children: item.label
|
|
1426
|
+
})
|
|
1427
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1428
|
+
className: "dsh-model-menu-check",
|
|
1429
|
+
children: item.selected === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
1430
|
+
})]
|
|
1431
|
+
}, item.id))
|
|
1432
|
+
}) : null, filteredGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuGroup, {
|
|
1433
|
+
label: group.name,
|
|
1434
|
+
children: group.items.map((item) => {
|
|
1435
|
+
const index = modelIndex++;
|
|
1436
|
+
const choice = item.choice;
|
|
1437
|
+
const checked = selected?.provider === choice.provider && selected.model === choice.model;
|
|
1438
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1439
|
+
ref: itemRef(),
|
|
1440
|
+
type: "button",
|
|
1441
|
+
role: "menuitemradio",
|
|
1442
|
+
"aria-checked": checked,
|
|
1443
|
+
id: `${id}-model-${index}`,
|
|
1444
|
+
tabIndex: showSearch ? -1 : 0,
|
|
1445
|
+
onFocus: () => setHighlightedIndex(index),
|
|
1446
|
+
"data-highlighted": index === activeModelIndex ? "" : void 0,
|
|
1447
|
+
className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
|
|
1448
|
+
onMouseMove: index === activeModelIndex ? void 0 : () => {
|
|
1449
|
+
if (showSearch) setHighlightedIndex(index);
|
|
1450
|
+
else itemRefs.current[leading.length + index]?.focus();
|
|
1451
|
+
},
|
|
1452
|
+
title: choice.label,
|
|
1453
|
+
onClick: () => {
|
|
1454
|
+
onPick({
|
|
1455
|
+
provider: choice.provider,
|
|
1456
|
+
model: choice.model
|
|
1457
|
+
});
|
|
1458
|
+
close(true);
|
|
1459
|
+
},
|
|
1460
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1461
|
+
className: "dsh-model-menu-optionCopy",
|
|
1462
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1463
|
+
className: "dsh-model-menu-modelName",
|
|
1464
|
+
children: modelMenuShortName(choice)
|
|
1465
|
+
})
|
|
1466
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1467
|
+
className: "dsh-model-menu-check",
|
|
1468
|
+
children: checked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
1469
|
+
})]
|
|
1470
|
+
}, `${choice.provider}/${choice.model}`);
|
|
1471
|
+
})
|
|
1472
|
+
}, group.provider))]
|
|
1473
|
+
}),
|
|
1474
|
+
filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1475
|
+
className: "dsh-model-menu-empty",
|
|
1476
|
+
role: "status",
|
|
1477
|
+
children: emptyLabel
|
|
1478
|
+
}) : null
|
|
1479
|
+
] }) : null,
|
|
1480
|
+
pane === "effort" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: effortRows.map((row) => {
|
|
1481
|
+
const checked = (selectedEffort ?? "") === row.id;
|
|
1482
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1483
|
+
ref: itemRef(),
|
|
1484
|
+
type: "button",
|
|
1485
|
+
role: "menuitemradio",
|
|
1486
|
+
"aria-checked": checked,
|
|
1487
|
+
className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
|
|
1488
|
+
onClick: () => {
|
|
1489
|
+
onPickEffort?.(row.id || void 0);
|
|
1490
|
+
close(true);
|
|
1491
|
+
},
|
|
1492
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1493
|
+
className: "dsh-model-menu-optionCopy",
|
|
1494
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1495
|
+
className: "dsh-model-menu-modelName",
|
|
1496
|
+
children: row.name
|
|
1497
|
+
})
|
|
1498
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1499
|
+
className: "dsh-model-menu-check",
|
|
1500
|
+
children: checked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
1501
|
+
})]
|
|
1502
|
+
}, row.id || "__default");
|
|
1503
|
+
}) }) : null
|
|
1504
|
+
]
|
|
1505
|
+
}), document.body) : null;
|
|
1506
|
+
const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
|
|
1507
|
+
"aria-haspopup": "menu",
|
|
1508
|
+
"aria-expanded": open,
|
|
1509
|
+
"aria-controls": open ? `${id}-menu` : void 0
|
|
1510
|
+
}) : anchor;
|
|
1511
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1512
|
+
ref: rootRef,
|
|
1513
|
+
className: `dsh-model-menu-root${className ? ` ${className}` : ""}`,
|
|
1514
|
+
onKeyDown,
|
|
1515
|
+
onBlur,
|
|
1516
|
+
onClick: (event) => {
|
|
1517
|
+
if (event.defaultPrevented) return;
|
|
1518
|
+
const button = event.target instanceof Element ? event.target.closest("button") : null;
|
|
1519
|
+
if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
|
|
1520
|
+
requestOpen();
|
|
1521
|
+
},
|
|
1522
|
+
onMouseDown: (event) => {
|
|
1523
|
+
if (event.target instanceof Element && menuRef.current?.contains(event.target) && event.target.closest("button") !== null) event.preventDefault();
|
|
1524
|
+
},
|
|
1525
|
+
children: [trigger, menu]
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* ModelMenu styles, transcribed from the official ModelSelect.module.css (the
|
|
1530
|
+
* published dsh-client-ui-model-selection bundle): card capped at
|
|
1531
|
+
* min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
|
|
1532
|
+
* 34px root cells at 13/20, a borderless 12px search row with a clear button,
|
|
1533
|
+
* sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
|
|
1534
|
+
* elevation at portal z-index 1100. The trigger mirrors the composer's 28px
|
|
1535
|
+
* model pill, chevron rotating open. MenuSurface paints fill and backdrop.
|
|
1536
|
+
* Only --dsw-* theme tokens may appear here — a literal color is a bug.
|
|
1537
|
+
*/
|
|
1538
|
+
const modelMenuCss = `
|
|
1539
|
+
.dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
|
|
1540
|
+
.dsh-model-menu-trigger {
|
|
1541
|
+
border-radius: var(--dsw-radius-sm);
|
|
1542
|
+
min-width: 0;
|
|
1543
|
+
max-width: min(360px, 45cqw);
|
|
1544
|
+
height: 28px;
|
|
1545
|
+
color: var(--dsw-alias-label-secondary);
|
|
1546
|
+
cursor: pointer;
|
|
1547
|
+
background: none;
|
|
1548
|
+
border: none;
|
|
1549
|
+
outline: none;
|
|
1550
|
+
align-items: center;
|
|
1551
|
+
gap: 4px;
|
|
1552
|
+
padding: 0 4px 0 8px;
|
|
1553
|
+
font-size: 13px;
|
|
1554
|
+
font-weight: 400;
|
|
1555
|
+
line-height: 20px;
|
|
1556
|
+
display: flex;
|
|
1557
|
+
}
|
|
1558
|
+
.dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1559
|
+
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
|
|
1560
|
+
.dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
1561
|
+
.dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
|
|
1562
|
+
.dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
|
|
1563
|
+
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform 0.12s; }
|
|
1564
|
+
.dsh-model-menu-chevronOpen { transform: rotate(180deg); }
|
|
1565
|
+
.dsh-model-menu-menu {
|
|
1566
|
+
z-index: 1100;
|
|
1567
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
1568
|
+
width: max-content;
|
|
1569
|
+
min-width: min(240px, 100vw - 32px);
|
|
1570
|
+
max-width: min(420px, 100vw - 32px);
|
|
1571
|
+
max-height: min(360px, 100vh - 96px);
|
|
1572
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
1573
|
+
color: var(--dsw-alias-label-primary);
|
|
1574
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
1575
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
1576
|
+
border: 0;
|
|
1577
|
+
flex-direction: column;
|
|
1578
|
+
padding: 4px;
|
|
1579
|
+
display: flex;
|
|
1580
|
+
position: fixed;
|
|
1581
|
+
overflow: hidden;
|
|
1582
|
+
}
|
|
1583
|
+
.dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
|
|
1584
|
+
.dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
|
|
1585
|
+
.dsh-model-menu-searchRow .dsh-model-menu-search { border-radius: var(--dsw-radius-md); background: none; border: 0 solid transparent; height: auto; padding: 5px 7px; display: flex; }
|
|
1586
|
+
.dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
|
|
1587
|
+
.dsh-model-menu-searchWithQuery { padding-right: 34px; }
|
|
1588
|
+
.dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
|
|
1589
|
+
.dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
|
|
1590
|
+
.dsh-model-menu-searchClear {
|
|
1591
|
+
width: 24px;
|
|
1592
|
+
height: 24px;
|
|
1593
|
+
color: var(--dsw-alias-label-secondary);
|
|
1594
|
+
cursor: pointer;
|
|
1595
|
+
background: none;
|
|
1596
|
+
border: none;
|
|
1597
|
+
border-radius: 50%;
|
|
1598
|
+
justify-content: center;
|
|
1599
|
+
align-items: center;
|
|
1600
|
+
padding: 0;
|
|
1601
|
+
display: inline-flex;
|
|
1602
|
+
position: absolute;
|
|
1603
|
+
top: 50%;
|
|
1604
|
+
right: 4px;
|
|
1605
|
+
transform: translateY(-50%);
|
|
1606
|
+
}
|
|
1607
|
+
.dsh-model-menu-searchClear:hover, .dsh-model-menu-searchClear:focus-visible { background: var(--dsw-alias-interactive-bg-hover); outline: none; }
|
|
1608
|
+
.dsh-model-menu-groups { min-height: 0; overflow-y: auto; }
|
|
1609
|
+
.dsh-model-menu-leading { border-bottom: 0.5px solid var(--dsw-alias-border-l2); margin-bottom: 3px; padding-bottom: 3px; }
|
|
1610
|
+
.dsh-model-menu-option {
|
|
1611
|
+
box-sizing: border-box;
|
|
1612
|
+
border-radius: var(--dsw-radius-md);
|
|
1613
|
+
width: auto;
|
|
1614
|
+
min-width: 100%;
|
|
1615
|
+
min-height: 34px;
|
|
1616
|
+
color: inherit;
|
|
1617
|
+
text-align: left;
|
|
1618
|
+
cursor: pointer;
|
|
1619
|
+
background: none;
|
|
1620
|
+
border: none;
|
|
1621
|
+
outline: none;
|
|
1622
|
+
align-items: center;
|
|
1623
|
+
gap: 6px;
|
|
1624
|
+
padding: 5px 7px;
|
|
1625
|
+
display: flex;
|
|
1626
|
+
}
|
|
1627
|
+
.dsh-model-menu-option:not(.dsh-model-menu-modelOption):hover:not(:disabled),
|
|
1628
|
+
.dsh-model-menu-option:focus-visible,
|
|
1629
|
+
.dsh-model-menu-optionActive:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1630
|
+
.dsh-model-menu-modelOption:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1631
|
+
.dsh-model-menu-selected { background: none; }
|
|
1632
|
+
.dsh-model-menu-option:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
1633
|
+
.dsh-model-menu-optionCopy { flex-direction: column; flex: 1; min-width: 0; display: flex; }
|
|
1634
|
+
.dsh-model-menu-modelName { color: inherit; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 400; line-height: 18px; overflow: hidden; }
|
|
1635
|
+
.dsh-model-menu-check { color: var(--dsw-alias-label-primary); flex: 0 0 14px; place-items: center; display: grid; }
|
|
1636
|
+
.dsh-model-menu-check svg { width: 14px; height: 14px; }
|
|
1637
|
+
.dsh-model-menu-cell {
|
|
1638
|
+
box-sizing: border-box;
|
|
1639
|
+
border-radius: var(--dsw-radius-md);
|
|
1640
|
+
width: auto;
|
|
1641
|
+
min-width: 100%;
|
|
1642
|
+
height: 34px;
|
|
1643
|
+
color: var(--dsw-alias-label-primary);
|
|
1644
|
+
cursor: pointer;
|
|
1645
|
+
text-align: left;
|
|
1646
|
+
background: none;
|
|
1647
|
+
border: none;
|
|
1648
|
+
outline: none;
|
|
1649
|
+
align-items: center;
|
|
1650
|
+
gap: 6px;
|
|
1651
|
+
padding: 0 8px;
|
|
1652
|
+
font-size: 13px;
|
|
1653
|
+
line-height: 20px;
|
|
1654
|
+
display: flex;
|
|
1655
|
+
}
|
|
1656
|
+
.dsh-model-menu-cell:hover, .dsh-model-menu-cell:focus-visible { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1657
|
+
.dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
|
|
1658
|
+
.dsh-model-menu-cellValue { text-overflow: ellipsis; white-space: nowrap; text-align: right; min-width: 0; color: var(--dsw-alias-label-tertiary); flex: auto; overflow: hidden; }
|
|
1659
|
+
.dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
|
|
1660
|
+
`;
|
|
1661
|
+
//#endregion
|
|
653
1662
|
//#region src/contracts.ts
|
|
654
1663
|
const RPC_CHANNEL = "/dsh-current-title";
|
|
655
1664
|
const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
@@ -658,6 +1667,8 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
|
658
1667
|
const titleCopy = {
|
|
659
1668
|
zh: {
|
|
660
1669
|
label: "自动标题",
|
|
1670
|
+
session: "当前会话",
|
|
1671
|
+
noSession: "没有打开的会话。打开一个会话后,可以在这里查看并重新生成标题。",
|
|
661
1672
|
pinned: "已手动命名",
|
|
662
1673
|
generating: "正在生成标题…",
|
|
663
1674
|
regenerate: "重新生成",
|
|
@@ -677,10 +1688,13 @@ const titleCopy = {
|
|
|
677
1688
|
retry: "重试",
|
|
678
1689
|
externalChange: "设置已在别处修改。提示词草稿已保留,请重新加载后确认。",
|
|
679
1690
|
syncUnavailable: "自动刷新不可用,请手动重新加载设置。",
|
|
680
|
-
reload: "重新加载(丢弃草稿)"
|
|
1691
|
+
reload: "重新加载(丢弃草稿)",
|
|
1692
|
+
defaults: "默认设置"
|
|
681
1693
|
},
|
|
682
1694
|
en: {
|
|
683
1695
|
label: "Auto Title",
|
|
1696
|
+
session: "Current session",
|
|
1697
|
+
noSession: "No session is open. Open one to see its title and regenerate it here.",
|
|
684
1698
|
pinned: "Renamed manually",
|
|
685
1699
|
generating: "Generating title…",
|
|
686
1700
|
regenerate: "Regenerate",
|
|
@@ -700,7 +1714,8 @@ const titleCopy = {
|
|
|
700
1714
|
retry: "Retry",
|
|
701
1715
|
externalChange: "Settings changed elsewhere. Prompt draft preserved; reload and review.",
|
|
702
1716
|
syncUnavailable: "Automatic refresh unavailable; reload settings manually.",
|
|
703
|
-
reload: "Reload (discard draft)"
|
|
1717
|
+
reload: "Reload (discard draft)",
|
|
1718
|
+
defaults: "Default settings"
|
|
704
1719
|
}
|
|
705
1720
|
};
|
|
706
1721
|
/** Only plugin-owned defaults are translated; model output and remote diagnostics stay intact. */
|
|
@@ -847,44 +1862,12 @@ const name = "dsh-current-title-client";
|
|
|
847
1862
|
const inject = [
|
|
848
1863
|
"slots",
|
|
849
1864
|
"connection",
|
|
850
|
-
|
|
851
|
-
"remote.session",
|
|
1865
|
+
...MODEL_CATALOG_INJECT,
|
|
852
1866
|
"sessions",
|
|
853
1867
|
"locale",
|
|
854
1868
|
"uiWorkspace",
|
|
855
1869
|
"uiSession"
|
|
856
1870
|
];
|
|
857
|
-
function catalogChoices(value, selected) {
|
|
858
|
-
const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
859
|
-
const root = (envelope?.ok === true && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value) ? envelope.value : void 0) ?? envelope ?? {};
|
|
860
|
-
const groups = Array.isArray(root.groups) ? root.groups : [];
|
|
861
|
-
const choices = [];
|
|
862
|
-
for (const group of groups) {
|
|
863
|
-
if (!group || typeof group !== "object") continue;
|
|
864
|
-
const row = group;
|
|
865
|
-
const provider = typeof row.id === "string" ? row.id : "";
|
|
866
|
-
const providerName = typeof row.name === "string" && row.name ? row.name : provider;
|
|
867
|
-
if (!provider || !Array.isArray(row.models)) continue;
|
|
868
|
-
for (const model of row.models) {
|
|
869
|
-
if (!model || typeof model !== "object") continue;
|
|
870
|
-
const item = model;
|
|
871
|
-
const id = typeof item.id === "string" ? item.id : "";
|
|
872
|
-
if (!id) continue;
|
|
873
|
-
const name = typeof item.name === "string" && item.name ? item.name : id;
|
|
874
|
-
choices.push({
|
|
875
|
-
provider,
|
|
876
|
-
model: id,
|
|
877
|
-
label: `${providerName} / ${name}`
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (selected.provider && selected.model && !choices.some((item) => item.provider === selected.provider && item.model === selected.model)) choices.push({
|
|
882
|
-
provider: selected.provider,
|
|
883
|
-
model: selected.model,
|
|
884
|
-
label: `${selected.provider} / ${selected.model}`
|
|
885
|
-
});
|
|
886
|
-
return choices;
|
|
887
|
-
}
|
|
888
1871
|
function TitleSettings(props) {
|
|
889
1872
|
const seat = useNativeSeat(props.client);
|
|
890
1873
|
const sessionId = seat.sessionId;
|
|
@@ -895,7 +1878,10 @@ function TitleSettings(props) {
|
|
|
895
1878
|
const [note, setNote] = (0, react.useState)("");
|
|
896
1879
|
const [error, setError] = (0, react.useState)("");
|
|
897
1880
|
const [prompt, setPrompt] = (0, react.useState)(void 0);
|
|
898
|
-
const
|
|
1881
|
+
const catalogState = useModelCatalog((0, react.useCallback)(() => readModelCatalog(props.client), [props.client]), { subscribe: (0, react.useCallback)((change) => props.client.connection.generation?.subscribe(change), [props.client]) });
|
|
1882
|
+
const { refresh: refreshCatalog } = catalogState;
|
|
1883
|
+
const choices = parseModelMenuChoices(catalogState.catalog, status?.settings?.model);
|
|
1884
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
899
1885
|
const [externalChange, setExternalChange] = (0, react.useState)(false);
|
|
900
1886
|
const [syncError, setSyncError] = (0, react.useState)("");
|
|
901
1887
|
const [loadingSettings, setLoadingSettings] = (0, react.useState)(true);
|
|
@@ -938,26 +1924,6 @@ function TitleSettings(props) {
|
|
|
938
1924
|
work,
|
|
939
1925
|
readGate
|
|
940
1926
|
]);
|
|
941
|
-
(0, react.useEffect)(() => {
|
|
942
|
-
const catalog = props.client.remote?.session?.modelCatalog;
|
|
943
|
-
if (!catalog) return void 0;
|
|
944
|
-
let live = true;
|
|
945
|
-
catalog().then((value) => {
|
|
946
|
-
if (live) setChoices(catalogChoices(value, status?.settings.model ?? {
|
|
947
|
-
provider: "",
|
|
948
|
-
model: ""
|
|
949
|
-
}));
|
|
950
|
-
}).catch(() => {
|
|
951
|
-
if (live) setChoices([]);
|
|
952
|
-
});
|
|
953
|
-
return () => {
|
|
954
|
-
live = false;
|
|
955
|
-
};
|
|
956
|
-
}, [
|
|
957
|
-
props.client,
|
|
958
|
-
status?.settings.model.provider,
|
|
959
|
-
status?.settings.model.model
|
|
960
|
-
]);
|
|
961
1927
|
(0, react.useEffect)(() => {
|
|
962
1928
|
const generation = work.beginLoad();
|
|
963
1929
|
reading.current = true;
|
|
@@ -1059,6 +2025,7 @@ function TitleSettings(props) {
|
|
|
1059
2025
|
const stop = props.subscribeSettings?.(() => {
|
|
1060
2026
|
if (!live) return;
|
|
1061
2027
|
setSyncError("");
|
|
2028
|
+
refreshCatalog();
|
|
1062
2029
|
if (dirty.current && !busyRef.current) {
|
|
1063
2030
|
external.current = true;
|
|
1064
2031
|
setExternalChange(true);
|
|
@@ -1071,17 +2038,25 @@ function TitleSettings(props) {
|
|
|
1071
2038
|
live = false;
|
|
1072
2039
|
stop?.();
|
|
1073
2040
|
};
|
|
1074
|
-
}, [
|
|
1075
|
-
|
|
1076
|
-
|
|
2041
|
+
}, [
|
|
2042
|
+
props.subscribeSettings,
|
|
2043
|
+
props.client,
|
|
2044
|
+
refreshCatalog
|
|
2045
|
+
]);
|
|
2046
|
+
const savedPrompt = status?.settings?.prompt ?? "";
|
|
2047
|
+
const savedModel = status?.settings?.model ?? {
|
|
1077
2048
|
provider: "",
|
|
1078
2049
|
model: ""
|
|
1079
2050
|
};
|
|
1080
|
-
const
|
|
1081
|
-
const
|
|
2051
|
+
const follow = !savedModel.provider || !savedModel.model;
|
|
2052
|
+
const selectedModel = follow ? void 0 : choices.find((choice) => choice.provider === savedModel.provider && choice.model === savedModel.model);
|
|
2053
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selectedModel, savedModel.reasoningEffort);
|
|
2054
|
+
const currentLabel = follow ? text.modelDefault : selectedModel?.label ?? `${savedModel.provider} / ${savedModel.model}`;
|
|
2055
|
+
const selectedEffort = efforts.find((item) => item.id === (savedModel.reasoningEffort || ""));
|
|
2056
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, seat.locale) : void 0;
|
|
1082
2057
|
const title = status?.session?.title;
|
|
1083
2058
|
const pinned = status?.session?.pinned === true;
|
|
1084
|
-
const weOwn = status?.support
|
|
2059
|
+
const weOwn = status?.support?.weOwn === true;
|
|
1085
2060
|
const canRegenerate = Boolean(sessionId) && weOwn && !busy && !loadingSettings;
|
|
1086
2061
|
function saveSettings(patch) {
|
|
1087
2062
|
if (!status || busyRef.current || reading.current || external.current) return;
|
|
@@ -1115,225 +2090,272 @@ function TitleSettings(props) {
|
|
|
1115
2090
|
className: "dsh-ui-hint",
|
|
1116
2091
|
children: text.loading
|
|
1117
2092
|
}) : null,
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
children: title
|
|
1122
|
-
}) : null,
|
|
1123
|
-
pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1124
|
-
className: "dsh-ui-meta",
|
|
1125
|
-
children: text.pinned
|
|
1126
|
-
}) : null,
|
|
1127
|
-
status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1128
|
-
role: "status",
|
|
1129
|
-
className: "dsh-ui-hint",
|
|
1130
|
-
children: text.generating
|
|
1131
|
-
}) : null,
|
|
1132
|
-
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1133
|
-
role: "status",
|
|
1134
|
-
className: "dsh-ui-hint",
|
|
1135
|
-
children: [text.inactive, status.support.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
|
|
1136
|
-
}) : null,
|
|
1137
|
-
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1138
|
-
role: "alert",
|
|
1139
|
-
className: "dsh-ui-error dsh-ui-wrap",
|
|
1140
|
-
children: titleMessage(error, seat.locale)
|
|
1141
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1142
|
-
variant: "outline",
|
|
1143
|
-
size: "sm",
|
|
1144
|
-
disabled: busy,
|
|
1145
|
-
onClick: () => {
|
|
1146
|
-
readGate.reset();
|
|
1147
|
-
refreshStatus();
|
|
1148
|
-
},
|
|
1149
|
-
children: text.retry
|
|
1150
|
-
})] }) : null,
|
|
1151
|
-
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1152
|
-
role: "status",
|
|
1153
|
-
className: "dsh-ui-notice",
|
|
1154
|
-
children: titleMessage(note, seat.locale)
|
|
1155
|
-
}) : null,
|
|
1156
|
-
externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1157
|
-
role: "alert",
|
|
1158
|
-
className: "dsh-ui-error",
|
|
1159
|
-
children: text.externalChange
|
|
1160
|
-
}) : null,
|
|
1161
|
-
syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1162
|
-
role: "status",
|
|
1163
|
-
className: "dsh-ui-hint",
|
|
1164
|
-
children: text.syncUnavailable
|
|
1165
|
-
}) : null,
|
|
1166
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1167
|
-
className: "dsh-ui-actions",
|
|
1168
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1169
|
-
variant: "ghost",
|
|
1170
|
-
size: "sm",
|
|
1171
|
-
disabled: busy || loadingSettings,
|
|
1172
|
-
onClick: () => {
|
|
1173
|
-
setPrompt(void 0);
|
|
1174
|
-
dirty.current = false;
|
|
1175
|
-
external.current = false;
|
|
1176
|
-
setExternalChange(false);
|
|
1177
|
-
readGate.reset();
|
|
1178
|
-
syncRef.current();
|
|
1179
|
-
},
|
|
1180
|
-
children: text.reload
|
|
1181
|
-
}), sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1182
|
-
variant: "outline",
|
|
1183
|
-
size: "md",
|
|
1184
|
-
disabled: !canRegenerate,
|
|
1185
|
-
onClick: () => {
|
|
1186
|
-
if (!sessionId) return;
|
|
1187
|
-
readGate.reset();
|
|
1188
|
-
const token = work.beginRequest();
|
|
1189
|
-
setError("");
|
|
1190
|
-
setNote("");
|
|
1191
|
-
runTitleRegenerateFlow({
|
|
1192
|
-
call,
|
|
1193
|
-
sessionId,
|
|
1194
|
-
isCurrent: () => work.isRequest(token),
|
|
1195
|
-
failedMessage: messages.failed,
|
|
1196
|
-
onBusy: changeBusy,
|
|
1197
|
-
onStatus: setStatus,
|
|
1198
|
-
onError: setError,
|
|
1199
|
-
onMarker: commitMarker
|
|
1200
|
-
});
|
|
1201
|
-
},
|
|
1202
|
-
children: text.regenerate
|
|
1203
|
-
}) : null]
|
|
1204
|
-
}),
|
|
1205
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1206
|
-
className: "dsh-ui-field",
|
|
2093
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
2094
|
+
className: "dsh-ui-card",
|
|
2095
|
+
"data-testid": "current-title-session",
|
|
1207
2096
|
children: [
|
|
1208
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1209
|
-
className: "dsh-ui-
|
|
1210
|
-
children: text.
|
|
2097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2098
|
+
className: "dsh-ui-heading",
|
|
2099
|
+
children: text.session
|
|
1211
2100
|
}),
|
|
1212
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
2101
|
+
title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2102
|
+
translate: "no",
|
|
2103
|
+
className: "dsh-ui-title dsh-ui-wrap",
|
|
2104
|
+
children: title
|
|
2105
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2106
|
+
className: "dsh-ui-hint",
|
|
2107
|
+
children: text.noSession
|
|
2108
|
+
}),
|
|
2109
|
+
pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2110
|
+
className: "dsh-ui-meta",
|
|
2111
|
+
children: text.pinned
|
|
2112
|
+
}) : null,
|
|
2113
|
+
status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2114
|
+
role: "status",
|
|
2115
|
+
className: "dsh-ui-hint",
|
|
2116
|
+
children: text.generating
|
|
2117
|
+
}) : null,
|
|
2118
|
+
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2119
|
+
role: "status",
|
|
2120
|
+
className: "dsh-ui-hint",
|
|
2121
|
+
children: [text.inactive, status.support?.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
|
|
2122
|
+
}) : null,
|
|
2123
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2124
|
+
role: "alert",
|
|
2125
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
2126
|
+
children: titleMessage(error, seat.locale)
|
|
2127
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2128
|
+
variant: "outline",
|
|
2129
|
+
size: "sm",
|
|
2130
|
+
disabled: busy,
|
|
2131
|
+
onClick: () => {
|
|
2132
|
+
readGate.reset();
|
|
2133
|
+
refreshStatus();
|
|
1222
2134
|
},
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
2135
|
+
children: text.retry
|
|
2136
|
+
})] }) : null,
|
|
2137
|
+
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2138
|
+
role: "status",
|
|
2139
|
+
className: "dsh-ui-notice",
|
|
2140
|
+
children: titleMessage(note, seat.locale)
|
|
2141
|
+
}) : null,
|
|
2142
|
+
externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2143
|
+
role: "alert",
|
|
2144
|
+
className: "dsh-ui-error",
|
|
2145
|
+
children: text.externalChange
|
|
2146
|
+
}) : null,
|
|
2147
|
+
syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2148
|
+
role: "status",
|
|
2149
|
+
className: "dsh-ui-hint",
|
|
2150
|
+
children: text.syncUnavailable
|
|
2151
|
+
}) : null,
|
|
2152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2153
|
+
className: "dsh-ui-actions",
|
|
2154
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2155
|
+
variant: "ghost",
|
|
2156
|
+
size: "sm",
|
|
2157
|
+
disabled: busy || loadingSettings,
|
|
2158
|
+
onClick: () => {
|
|
1228
2159
|
setPrompt(void 0);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
2160
|
+
dirty.current = false;
|
|
2161
|
+
external.current = false;
|
|
2162
|
+
setExternalChange(false);
|
|
2163
|
+
readGate.reset();
|
|
2164
|
+
syncRef.current();
|
|
2165
|
+
},
|
|
2166
|
+
children: text.reload
|
|
2167
|
+
}), sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2168
|
+
variant: "outline",
|
|
2169
|
+
size: "md",
|
|
2170
|
+
disabled: !canRegenerate,
|
|
2171
|
+
onClick: () => {
|
|
2172
|
+
if (!sessionId) return;
|
|
2173
|
+
readGate.reset();
|
|
2174
|
+
const token = work.beginRequest();
|
|
2175
|
+
setError("");
|
|
2176
|
+
setNote("");
|
|
2177
|
+
runTitleRegenerateFlow({
|
|
2178
|
+
call,
|
|
2179
|
+
sessionId,
|
|
2180
|
+
isCurrent: () => work.isRequest(token),
|
|
2181
|
+
failedMessage: messages.failed,
|
|
2182
|
+
onBusy: changeBusy,
|
|
2183
|
+
onStatus: setStatus,
|
|
2184
|
+
onError: setError,
|
|
2185
|
+
onMarker: commitMarker
|
|
2186
|
+
});
|
|
2187
|
+
},
|
|
2188
|
+
children: text.regenerate
|
|
2189
|
+
}) : null]
|
|
2190
|
+
})
|
|
2191
|
+
]
|
|
2192
|
+
}),
|
|
2193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2194
|
+
className: "dsh-ui-section",
|
|
2195
|
+
"data-testid": "current-title-defaults",
|
|
2196
|
+
children: [
|
|
2197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2198
|
+
className: "dsh-ui-heading",
|
|
2199
|
+
children: text.defaults
|
|
2200
|
+
}),
|
|
2201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2202
|
+
className: "dsh-ui-field",
|
|
2203
|
+
children: [
|
|
2204
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2205
|
+
className: "dsh-ui-label",
|
|
2206
|
+
children: text.prompt
|
|
2207
|
+
}),
|
|
2208
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2209
|
+
className: "dsh-ui-textarea ct-prompt",
|
|
2210
|
+
value: status ? promptFieldValue(prompt, savedPrompt) : "",
|
|
2211
|
+
maxLength: 4e3,
|
|
2212
|
+
rows: 5,
|
|
2213
|
+
disabled: busy || loadingSettings || !status,
|
|
2214
|
+
onChange: (event) => {
|
|
2215
|
+
dirty.current = true;
|
|
2216
|
+
setPrompt(event.target.value);
|
|
2217
|
+
setNote("");
|
|
1249
2218
|
},
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
2219
|
+
onBlur: () => {
|
|
2220
|
+
if (!status || busyRef.current || reading.current || external.current) return;
|
|
2221
|
+
const next = promptToSave(prompt, savedPrompt);
|
|
2222
|
+
if (next === void 0) {
|
|
2223
|
+
dirty.current = false;
|
|
2224
|
+
setPrompt(void 0);
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
readGate.reset();
|
|
2228
|
+
const token = work.beginRequest();
|
|
2229
|
+
setError("");
|
|
2230
|
+
runTitleSettingsSave({
|
|
2231
|
+
call,
|
|
2232
|
+
prompt: next,
|
|
2233
|
+
expectedRevision: status.settings.revision,
|
|
2234
|
+
isCurrent: () => work.isRequest(token),
|
|
2235
|
+
savedMessage: messages.saved,
|
|
2236
|
+
failedMessage: messages.failed,
|
|
2237
|
+
onBusy: changeBusy,
|
|
2238
|
+
onSettings: (settings) => {
|
|
2239
|
+
setStatus((current) => current ? {
|
|
2240
|
+
...current,
|
|
2241
|
+
settings
|
|
2242
|
+
} : current);
|
|
2243
|
+
dirty.current = false;
|
|
2244
|
+
setPrompt(void 0);
|
|
2245
|
+
},
|
|
2246
|
+
onNote: setNote,
|
|
2247
|
+
onError: setError
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
}),
|
|
2251
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2252
|
+
className: "dsh-ui-help",
|
|
2253
|
+
children: text.promptHint
|
|
2254
|
+
})
|
|
2255
|
+
]
|
|
1254
2256
|
}),
|
|
1255
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1256
|
-
className: "dsh-ui-
|
|
1257
|
-
children:
|
|
2257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2258
|
+
className: "dsh-ui-field",
|
|
2259
|
+
children: [
|
|
2260
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2261
|
+
className: "dsh-ui-label",
|
|
2262
|
+
children: text.model
|
|
2263
|
+
}),
|
|
2264
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
2265
|
+
state: catalogState,
|
|
2266
|
+
locale: seat.locale
|
|
2267
|
+
}),
|
|
2268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
2269
|
+
locale: seat.locale,
|
|
2270
|
+
open: menuOpen && Boolean(status) && !busy && !loadingSettings,
|
|
2271
|
+
onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
|
|
2272
|
+
listClassName: "ct-root",
|
|
2273
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2274
|
+
type: "button",
|
|
2275
|
+
className: "dsh-model-menu-trigger",
|
|
2276
|
+
disabled: busy || loadingSettings || !status,
|
|
2277
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2278
|
+
children: [
|
|
2279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2280
|
+
className: "dsh-model-menu-triggerLabel",
|
|
2281
|
+
children: currentLabel
|
|
2282
|
+
}),
|
|
2283
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2284
|
+
className: "dsh-model-menu-triggerEffort",
|
|
2285
|
+
children: effortName ?? text.modelDefault
|
|
2286
|
+
}) : null,
|
|
2287
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2288
|
+
]
|
|
2289
|
+
}),
|
|
2290
|
+
choices,
|
|
2291
|
+
selected: follow ? void 0 : {
|
|
2292
|
+
provider: savedModel.provider,
|
|
2293
|
+
model: savedModel.model
|
|
2294
|
+
},
|
|
2295
|
+
onPick: (picked) => saveSettings({ model: {
|
|
2296
|
+
provider: picked.provider,
|
|
2297
|
+
model: picked.model
|
|
2298
|
+
} }),
|
|
2299
|
+
leading: [{
|
|
2300
|
+
id: "default",
|
|
2301
|
+
label: text.modelDefault,
|
|
2302
|
+
selected: follow
|
|
2303
|
+
}],
|
|
2304
|
+
onPickLeading: () => saveSettings({ model: {
|
|
2305
|
+
provider: "",
|
|
2306
|
+
model: ""
|
|
2307
|
+
} }),
|
|
2308
|
+
efforts,
|
|
2309
|
+
selectedEffort: savedModel.reasoningEffort || "",
|
|
2310
|
+
onPickEffort: (effort) => saveSettings({ model: follow ? {
|
|
2311
|
+
provider: "",
|
|
2312
|
+
model: ""
|
|
2313
|
+
} : {
|
|
2314
|
+
provider: savedModel.provider,
|
|
2315
|
+
model: savedModel.model,
|
|
2316
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
2317
|
+
} }),
|
|
2318
|
+
modelLabel: text.model,
|
|
2319
|
+
modelValue: currentLabel,
|
|
2320
|
+
defaultEffortLabel: text.modelDefault
|
|
2321
|
+
})
|
|
2322
|
+
]
|
|
2323
|
+
}),
|
|
2324
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2325
|
+
className: "dsh-ui-field",
|
|
2326
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2327
|
+
className: "dsh-ui-label",
|
|
2328
|
+
children: text.cadence
|
|
2329
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2330
|
+
className: "dsh-ui-select",
|
|
2331
|
+
value: status?.settings?.cadence ?? "all-prompts",
|
|
2332
|
+
disabled: busy || loadingSettings || !status,
|
|
2333
|
+
onChange: (event) => {
|
|
2334
|
+
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
2335
|
+
},
|
|
2336
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2337
|
+
value: "all-prompts",
|
|
2338
|
+
children: text.cadenceAll
|
|
2339
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2340
|
+
value: "first-prompt",
|
|
2341
|
+
children: text.cadenceFirst
|
|
2342
|
+
})]
|
|
2343
|
+
})]
|
|
1258
2344
|
})
|
|
1259
2345
|
]
|
|
1260
|
-
}),
|
|
1261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1262
|
-
className: "dsh-ui-field",
|
|
1263
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1264
|
-
className: "dsh-ui-label",
|
|
1265
|
-
children: text.model
|
|
1266
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1267
|
-
className: "dsh-ui-select",
|
|
1268
|
-
value: modelKey,
|
|
1269
|
-
disabled: busy || loadingSettings || !status,
|
|
1270
|
-
onChange: (event) => {
|
|
1271
|
-
const value = event.target.value;
|
|
1272
|
-
const choice = selectableChoices.find((item) => `${item.provider}\u001f${item.model}` === value);
|
|
1273
|
-
saveSettings({ model: choice ? {
|
|
1274
|
-
provider: choice.provider,
|
|
1275
|
-
model: choice.model
|
|
1276
|
-
} : {
|
|
1277
|
-
provider: "",
|
|
1278
|
-
model: ""
|
|
1279
|
-
} });
|
|
1280
|
-
},
|
|
1281
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1282
|
-
value: "",
|
|
1283
|
-
children: text.modelDefault
|
|
1284
|
-
}), selectableChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1285
|
-
value: `${choice.provider}\u001f${choice.model}`,
|
|
1286
|
-
children: choice.label
|
|
1287
|
-
}, `${choice.provider}\u001f${choice.model}`))]
|
|
1288
|
-
})]
|
|
1289
|
-
}),
|
|
1290
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1291
|
-
className: "dsh-ui-field",
|
|
1292
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1293
|
-
className: "dsh-ui-label",
|
|
1294
|
-
children: text.cadence
|
|
1295
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1296
|
-
className: "dsh-ui-select",
|
|
1297
|
-
value: status?.settings.cadence ?? "all-prompts",
|
|
1298
|
-
disabled: busy || loadingSettings || !status,
|
|
1299
|
-
onChange: (event) => {
|
|
1300
|
-
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
1301
|
-
},
|
|
1302
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1303
|
-
value: "all-prompts",
|
|
1304
|
-
children: text.cadenceAll
|
|
1305
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1306
|
-
value: "first-prompt",
|
|
1307
|
-
children: text.cadenceFirst
|
|
1308
|
-
})]
|
|
1309
|
-
})]
|
|
1310
2346
|
})
|
|
1311
2347
|
]
|
|
1312
2348
|
});
|
|
1313
2349
|
}
|
|
1314
|
-
const styles = `${officialUiCss("ct-root")}
|
|
2350
|
+
const styles = `${officialUiCss("ct-root")}${modelMenuCss}
|
|
1315
2351
|
.ct-root { max-width: 760px; }
|
|
1316
|
-
.ct-prompt {
|
|
1317
|
-
box-sizing: border-box;
|
|
1318
|
-
width: 100%;
|
|
1319
|
-
min-width: 0;
|
|
1320
|
-
min-height: 96px;
|
|
1321
|
-
resize: vertical;
|
|
1322
|
-
padding: 8px 12px;
|
|
1323
|
-
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1324
|
-
border-radius: var(--dsw-radius-md);
|
|
1325
|
-
background: var(--dsw-alias-bg-layer-3);
|
|
1326
|
-
font: inherit;
|
|
1327
|
-
font-size: 13px;
|
|
1328
|
-
line-height: 1.5;
|
|
1329
|
-
color: var(--dsw-alias-label-primary);
|
|
1330
|
-
}
|
|
1331
|
-
.ct-prompt:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
1332
|
-
.ct-prompt::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
2352
|
+
.ct-prompt { min-height: 96px; }
|
|
1333
2353
|
`;
|
|
1334
2354
|
function apply(ctx) {
|
|
1335
2355
|
const client = ctx;
|
|
1336
|
-
const
|
|
2356
|
+
const packageName = "@klarkxy/dsh-current-title";
|
|
2357
|
+
mountModelSettingsChanges(ctx, packageName);
|
|
2358
|
+
const settingsSubscriber = createModelSettingsSubscriber(ctx, packageName, modelSettingsChangesIdentity(packageName));
|
|
1337
2359
|
const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
|
|
1338
2360
|
const marker = createTitleClientMarker();
|
|
1339
2361
|
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|