@sqdr2/dsh-fusion 0.3.2 → 0.3.4
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/CHANGELOG.md +177 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/RELEASING.md +8 -2
- package/RELEASING.zh.md +7 -2
- package/dshx.yml +3 -0
- package/lib/client.js +356 -88
- package/lib/client.js.map +1 -1
- package/lib/dsh-fusion.js +590 -220
- package/lib/types/client/FusionPermission.d.ts +15 -0
- package/lib/types/client/FusionSettings.d.ts +9 -1
- package/lib/types/client/status-polling.d.ts +6 -2
- package/lib/types/contracts.d.ts +2 -0
- package/lib/types/host/cache-policy.d.ts +16 -6
- package/lib/types/host/native-role-sandbox.d.ts +10 -2
- package/lib/types/host/native-selection.d.ts +15 -5
- package/lib/types/host/pairs.d.ts +47 -0
- package/lib/types/host/settings.d.ts +23 -4
- package/lib/types/identity.d.ts +16 -14
- package/lib/types/status.d.ts +17 -1
- package/package.json +62 -60
- package/src/client/FusionHint.tsx +11 -4
- package/src/client/FusionPermission.tsx +52 -0
- package/src/client/FusionSettings.tsx +129 -30
- package/src/client/index.ts +10 -5
- package/src/client/status-polling.ts +9 -4
- package/src/contracts.ts +2 -0
- package/src/dsh-fusion.ts +130 -54
- package/src/host/cache-policy.ts +18 -6
- package/src/host/native-role-sandbox.ts +18 -6
- package/src/host/native-scopes.ts +2 -2
- package/src/host/native-selection.ts +65 -21
- package/src/host/native-worker-brief.ts +18 -5
- package/src/host/pairs.ts +180 -0
- package/src/host/settings.ts +45 -12
- package/src/host/status.ts +24 -1
- package/src/identity.ts +39 -16
- package/src/status.ts +17 -1
package/lib/client.js
CHANGED
|
@@ -51,10 +51,19 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
*/
|
|
52
52
|
/** The HTTP path the panel's own views and actions are served from. */
|
|
53
53
|
const FUSION_API_PATH = "/api/fusion";
|
|
54
|
+
/**
|
|
55
|
+
* The model ids this provider advertises: `auto` plus one generated
|
|
56
|
+
* `pair-xxxxxxxx` per additional combo. The browser bundle inlines this module,
|
|
57
|
+
* so the namespace shape is fixed here rather than read from a settings
|
|
58
|
+
* document at import time.
|
|
59
|
+
*/
|
|
60
|
+
const FUSION_MODEL_PATTERN = /^(?:auto|pair-[a-z0-9]{6,12})$/;
|
|
61
|
+
/** Whether a model id names a Fusion combo on this provider. */
|
|
62
|
+
const isFusionModel = (model) => typeof model === "string" && FUSION_MODEL_PATTERN.test(model);
|
|
54
63
|
/** Whether a provider route belongs to this plugin, under either name. */
|
|
55
64
|
const isFusionProvider = (provider) => provider === "dsh-fusion" || provider === "dsh-model-fusion";
|
|
56
|
-
/** Whether a route names
|
|
57
|
-
const isFusionSelection = (route) => isFusionProvider(route?.provider) && route?.model
|
|
65
|
+
/** Whether a route names a Fusion combo, under either provider spelling. */
|
|
66
|
+
const isFusionSelection = (route) => isFusionProvider(route?.provider) && isFusionModel(route?.model);
|
|
58
67
|
//#endregion
|
|
59
68
|
//#region src/client/FusionHistory.tsx
|
|
60
69
|
function formatCompact(tokens) {
|
|
@@ -676,6 +685,29 @@ window.__ModuleLoader__.load({
|
|
|
676
685
|
});
|
|
677
686
|
}
|
|
678
687
|
//#endregion
|
|
688
|
+
//#region src/host/settings.ts
|
|
689
|
+
/**
|
|
690
|
+
* The routes a saved choice still names that the current catalog no longer
|
|
691
|
+
* offers: a provider that was renamed or removed, or a model that was retired.
|
|
692
|
+
*
|
|
693
|
+
* Reading a saved combo tolerates a stale route so the row stays visible and
|
|
694
|
+
* editable, but saving one does not. Without this, the page can only say that
|
|
695
|
+
* *some* model is gone, which is unusable when the stale route is not the one
|
|
696
|
+
* being edited — the user then cannot tell which combo to fix.
|
|
697
|
+
*/
|
|
698
|
+
function missingRoutes(value, catalog) {
|
|
699
|
+
if (!value) return [];
|
|
700
|
+
const stale = (role, route) => route !== void 0 && !catalog.groups.find((group) => group.id === route.provider)?.models.find((model) => model.id === route.model) ? [{
|
|
701
|
+
role,
|
|
702
|
+
route
|
|
703
|
+
}] : [];
|
|
704
|
+
return [
|
|
705
|
+
...stale("lead", value.lead),
|
|
706
|
+
...stale("worker", value.worker),
|
|
707
|
+
...stale("compactor", value.compactor?.route)
|
|
708
|
+
];
|
|
709
|
+
}
|
|
710
|
+
//#endregion
|
|
679
711
|
//#region src/client/settings-events.ts
|
|
680
712
|
const listeners = /* @__PURE__ */ new Set();
|
|
681
713
|
const onSettingsChanged = (listener) => {
|
|
@@ -689,7 +721,7 @@ window.__ModuleLoader__.load({
|
|
|
689
721
|
};
|
|
690
722
|
//#endregion
|
|
691
723
|
//#region src/client/FusionSettings.tsx
|
|
692
|
-
const styles$
|
|
724
|
+
const styles$2 = `
|
|
693
725
|
.fusion-page{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;flex:1 1 auto;overflow:auto;display:flex;flex-direction:column;align-items:center;scrollbar-gutter:stable;padding:0 clamp(24px,4vw,48px) 48px;--dsh-scrollbar-width:9px;--dsh-scrollbar-thumb-border:2px}
|
|
694
726
|
.fusion-settings{box-sizing:border-box;width:100%;max-width:680px;margin:0 auto;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:18px}
|
|
695
727
|
.fusion-settings h2{margin:0;font-size:22px;line-height:30px;font-weight:600}
|
|
@@ -710,8 +742,28 @@ window.__ModuleLoader__.load({
|
|
|
710
742
|
.fusion-settings details{font-size:13px;line-height:21px}
|
|
711
743
|
.fusion-settings summary{cursor:pointer;font-weight:500}
|
|
712
744
|
.fusion-settings .fusion-limits{display:flex;flex-direction:column;gap:12px;padding-top:14px}
|
|
713
|
-
.fusion-settings input[type=number]{min-width:0;width:100%;box-sizing:border-box;background-color:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:8px;font:inherit}
|
|
745
|
+
.fusion-settings :is(input[type=number],input[type=text]){min-width:0;width:100%;box-sizing:border-box;background-color:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:8px;font:inherit}
|
|
714
746
|
.fusion-settings .fusion-consent{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:18px}
|
|
747
|
+
.fusion-combos{display:flex;flex-direction:column;gap:8px}
|
|
748
|
+
.fusion-settings .fusion-combo{display:flex;align-items:stretch;padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background-color:var(--dsw-alias-bg-layer-1);font-size:13px;overflow:hidden;transition:border-color .15s ease,background-color .15s ease}
|
|
749
|
+
.fusion-settings .fusion-combo:hover{background-color:var(--dsw-alias-interactive-bg-hover)}
|
|
750
|
+
.fusion-settings .fusion-combo[data-active=true]{border-color:var(--dsw-alias-brand-primary);background-color:var(--dsw-alias-interactive-bg-hover-accent)}
|
|
751
|
+
.fusion-settings .fusion-combo-main{flex:1 1 auto;display:flex;align-items:center;gap:10px;min-width:0;padding:11px 12px;background:none;border:0;border-radius:0;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
752
|
+
.fusion-settings .fusion-combo-main:hover{background:none}
|
|
753
|
+
.fusion-settings .fusion-combo :is(.fusion-combo-main,.fusion-combo-actions):focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
754
|
+
.fusion-settings .fusion-combo-check{position:relative;flex:none;width:14px;height:14px;border:1.5px solid var(--dsw-alias-border-l2);border-radius:50%;transition:border-color .15s ease}
|
|
755
|
+
.fusion-settings .fusion-combo:hover .fusion-combo-check{border-color:var(--dsw-alias-label-tertiary)}
|
|
756
|
+
.fusion-settings .fusion-combo[data-active=true] .fusion-combo-check{border-color:var(--dsw-alias-brand-primary)}
|
|
757
|
+
.fusion-settings .fusion-combo[data-active=true] .fusion-combo-check::after{content:'';position:absolute;inset:2.5px;border-radius:50%;background:var(--dsw-alias-brand-primary)}
|
|
758
|
+
.fusion-settings .fusion-combo-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
759
|
+
.fusion-settings .fusion-combo-name{font-weight:600;overflow-wrap:anywhere}
|
|
760
|
+
.fusion-settings .fusion-combo-routes{color:var(--dsw-alias-label-secondary);font-size:12px;overflow-wrap:anywhere}
|
|
761
|
+
.fusion-settings .fusion-combo-stale{color:var(--dsw-alias-state-error-primary);font-size:12px;overflow-wrap:anywhere}
|
|
762
|
+
.fusion-settings .fusion-combo-actions{flex:none;align-self:stretch;display:flex;align-items:center;padding:0 14px;border:0;border-left:1px solid var(--dsw-alias-border-l1);border-radius:0;background:none;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;transition:color .15s ease,background-color .15s ease}
|
|
763
|
+
.fusion-settings .fusion-combo-actions:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);background-color:var(--dsw-alias-interactive-bg-hover-danger)}
|
|
764
|
+
.fusion-settings .fusion-combo-actions:disabled{opacity:.4;cursor:default}
|
|
765
|
+
.fusion-settings .fusion-combo-add{align-self:flex-start;padding:8px 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:none;color:var(--dsw-alias-label-secondary);font-size:13px}
|
|
766
|
+
.fusion-settings .fusion-combo-add:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
715
767
|
.fusion-cache{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);padding:16px;display:flex;flex-direction:column;gap:12px}
|
|
716
768
|
.fusion-cache h3{margin:0}
|
|
717
769
|
.fusion-cache-summary{cursor:pointer;font-weight:600;font-size:14px;display:flex;align-items:center;justify-content:space-between;list-style:none;user-select:none;padding:2px 0}
|
|
@@ -773,6 +825,7 @@ window.__ModuleLoader__.load({
|
|
|
773
825
|
const lang = useLang();
|
|
774
826
|
const name = role === "compactor" ? tr(lang, "压缩", "Compaction") : role;
|
|
775
827
|
const model = catalog.groups.find((group) => group.id === route?.provider)?.models.find((model) => model.id === route?.model);
|
|
828
|
+
const stale = route !== void 0 && model === void 0;
|
|
776
829
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", { children: [
|
|
777
830
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: name }),
|
|
778
831
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: role === "Lead" ? tr(lang, "回答问题、制定方案并审查结果。", "Answers, plans and reviews.") : role === "Sidekick" ? tr(lang, "执行命令和修改,接收反馈后继续。", "Runs commands and makes changes, then continues with feedback.") : tr(lang, "整理较早的上下文,供 Lead 或 Worker 继续工作。", "Summarises older context so the Lead or Sidekick can continue.") }),
|
|
@@ -790,21 +843,33 @@ window.__ModuleLoader__.load({
|
|
|
790
843
|
model
|
|
791
844
|
});
|
|
792
845
|
},
|
|
793
|
-
children: [
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
value: key(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
846
|
+
children: [
|
|
847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
848
|
+
value: "",
|
|
849
|
+
disabled: role !== "compactor",
|
|
850
|
+
children: role === "compactor" ? tr(lang, "使用当前角色模型", "Use the role's own model") : tr(lang, "选择已连接的模型", "Choose a connected model")
|
|
851
|
+
}),
|
|
852
|
+
stale && route && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
853
|
+
value: key(route),
|
|
854
|
+
disabled: true,
|
|
855
|
+
children: tr(lang, `已不可用:${route.provider}/${route.model}`, `No longer available: ${route.provider}/${route.model}`)
|
|
856
|
+
}),
|
|
857
|
+
catalog.groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("optgroup", {
|
|
858
|
+
label: group.name,
|
|
859
|
+
children: group.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
860
|
+
value: key({
|
|
861
|
+
provider: group.id,
|
|
862
|
+
model: model.id
|
|
863
|
+
}),
|
|
864
|
+
children: model.name
|
|
865
|
+
}, model.id))
|
|
866
|
+
}, group.id))
|
|
867
|
+
]
|
|
807
868
|
})] }),
|
|
869
|
+
stale && route && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
870
|
+
role: "alert",
|
|
871
|
+
children: tr(lang, `这个角色保存的模型 ${route.provider}/${route.model} 已不可用,请重新选择。`, `This role's saved model ${route.provider}/${route.model} is no longer available; choose another.`)
|
|
872
|
+
}),
|
|
808
873
|
model?.reasoning && route && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [tr(lang, "推理强度", "Reasoning"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
809
874
|
"aria-label": tr(lang, `${name} 推理强度`, `${name} reasoning`),
|
|
810
875
|
value: route.reasoningEffort ?? "",
|
|
@@ -825,20 +890,35 @@ window.__ModuleLoader__.load({
|
|
|
825
890
|
})] })
|
|
826
891
|
] });
|
|
827
892
|
}
|
|
893
|
+
let draftKeys = 0;
|
|
894
|
+
const toDraft = (pairs) => pairs.map(({ id, name, pair }) => ({
|
|
895
|
+
key: id,
|
|
896
|
+
id,
|
|
897
|
+
name,
|
|
898
|
+
pair
|
|
899
|
+
}));
|
|
828
900
|
function FusionSettings() {
|
|
829
901
|
const lang = useLang();
|
|
830
902
|
const [state, setState] = (0, react.useState)();
|
|
831
|
-
const [draft, setDraft] = (0, react.useState)(
|
|
903
|
+
const [draft, setDraft] = (0, react.useState)([]);
|
|
904
|
+
const [active, setActive] = (0, react.useState)(0);
|
|
832
905
|
const [error, setError] = (0, react.useState)();
|
|
833
906
|
const [notice, setNotice] = (0, react.useState)();
|
|
834
907
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
908
|
+
const current = draft[Math.min(active, draft.length - 1)];
|
|
909
|
+
const routeName = (route) => route ? state?.catalog.groups.find((group) => group.id === route.provider)?.models.find((model) => model.id === route.model)?.name ?? `${route.provider}/${route.model}` : "—";
|
|
910
|
+
const roleLabel = (role) => role === "lead" ? tr(lang, "Lead", "Lead") : role === "worker" ? tr(lang, "Sidekick", "Sidekick") : tr(lang, "压缩模型", "Compaction model");
|
|
911
|
+
const update = (change) => setDraft((value) => value.map((combo, index) => combo.key === current?.key ? change(combo) : combo));
|
|
912
|
+
const chooseModel = (role, route) => update((combo) => ({
|
|
913
|
+
...combo,
|
|
914
|
+
pair: {
|
|
915
|
+
...combo.pair,
|
|
916
|
+
[role]: route,
|
|
917
|
+
...combo.pair.cacheKeepalive ? { cacheKeepalive: {
|
|
918
|
+
...combo.pair.cacheKeepalive,
|
|
919
|
+
[role]: role === "lead" ? "auto" : false
|
|
920
|
+
} } : {}
|
|
921
|
+
}
|
|
842
922
|
}));
|
|
843
923
|
(0, react.useEffect)(() => {
|
|
844
924
|
const abort = new AbortController();
|
|
@@ -851,24 +931,54 @@ window.__ModuleLoader__.load({
|
|
|
851
931
|
return response.json();
|
|
852
932
|
}).then((value) => {
|
|
853
933
|
setState(value);
|
|
854
|
-
setDraft(value.
|
|
934
|
+
setDraft(toDraft(value.pairs));
|
|
935
|
+
setActive(0);
|
|
855
936
|
}).catch((error) => {
|
|
856
937
|
if (!abort.signal.aborted) setError(String(error));
|
|
857
938
|
});
|
|
858
939
|
return () => abort.abort();
|
|
859
940
|
}, []);
|
|
941
|
+
const addCombo = () => {
|
|
942
|
+
draftKeys += 1;
|
|
943
|
+
setDraft((value) => [...value, {
|
|
944
|
+
key: `new-${draftKeys}`,
|
|
945
|
+
name: tr(lang, `组合 ${value.length + 1}`, `Combo ${value.length + 1}`),
|
|
946
|
+
pair: {}
|
|
947
|
+
}]);
|
|
948
|
+
setActive(draft.length);
|
|
949
|
+
};
|
|
950
|
+
const removeCombo = (index) => {
|
|
951
|
+
setDraft((value) => value.filter((_, position) => position !== index));
|
|
952
|
+
setActive(Math.max(0, Math.min(active > index ? active - 1 : active, draft.length - 2)));
|
|
953
|
+
};
|
|
860
954
|
const save = async () => {
|
|
861
|
-
if (!state
|
|
955
|
+
if (!state) return;
|
|
956
|
+
const invalid = draft.findIndex((combo) => !combo.pair.lead || !combo.pair.worker || !combo.name.trim());
|
|
957
|
+
if (invalid >= 0) {
|
|
958
|
+
setError(tr(lang, `第 ${invalid + 1} 个组合还不完整:需要名称、Lead 和 Sidekick。`, `Combo ${invalid + 1} is incomplete: it needs a name, a Lead and a Sidekick.`));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const stale = draft.flatMap((combo, index) => missingRoutes(combo.pair, state.catalog).map(({ role, route }) => tr(lang, `第 ${index + 1} 个组合「${combo.name.trim() || tr(lang, "未命名组合", "Untitled combo")}」的 ${roleLabel(role)} 模型已不可用:${route.provider}/${route.model}。请为该角色重新选择模型。`, `Combo ${index + 1} "${combo.name.trim() || "Untitled combo"}" still names a ${roleLabel(role)} model that is no longer available: ${route.provider}/${route.model}. Choose a model for that role again.`)));
|
|
962
|
+
if (stale.length) {
|
|
963
|
+
setError(stale.join(tr(lang, " ", " ")));
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
862
966
|
setSaving(true);
|
|
863
967
|
setError(void 0);
|
|
864
968
|
setNotice(void 0);
|
|
865
969
|
try {
|
|
866
|
-
const {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
970
|
+
const pairs = draft.map(({ id, name, pair }) => {
|
|
971
|
+
const { outputTokens, ...rest } = pair;
|
|
972
|
+
const limits = Object.fromEntries(Object.entries(outputTokens ?? {}).filter(([, tokens]) => Number.isSafeInteger(tokens) && Number(tokens) > 0));
|
|
973
|
+
return {
|
|
974
|
+
...id ? { id } : {},
|
|
975
|
+
name: name.trim(),
|
|
976
|
+
pair: {
|
|
977
|
+
...rest,
|
|
978
|
+
...Object.keys(limits).length ? { outputTokens: limits } : {}
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
});
|
|
872
982
|
const response = await fetch(FUSION_API_PATH, {
|
|
873
983
|
method: "POST",
|
|
874
984
|
credentials: "same-origin",
|
|
@@ -876,37 +986,49 @@ window.__ModuleLoader__.load({
|
|
|
876
986
|
body: JSON.stringify({
|
|
877
987
|
action: "configure",
|
|
878
988
|
revision: state.revision,
|
|
879
|
-
|
|
989
|
+
pairs
|
|
880
990
|
})
|
|
881
991
|
});
|
|
882
992
|
const result = await response.json();
|
|
883
993
|
if (!response.ok) throw new Error(result.error ?? tr(lang, "保存失败,请刷新后重试", "Save failed; refresh and try again"));
|
|
884
|
-
const
|
|
994
|
+
const confirmed = await fetch(`${FUSION_API_PATH}?view=settings`, {
|
|
885
995
|
credentials: "same-origin",
|
|
886
996
|
cache: "no-store"
|
|
887
997
|
});
|
|
888
|
-
if (!
|
|
889
|
-
const
|
|
890
|
-
setState(
|
|
891
|
-
setDraft(
|
|
998
|
+
if (!confirmed.ok) throw new Error(tr(lang, "配置已提交,但暂时无法确认,请刷新", "Saved, but not confirmed yet; refresh"));
|
|
999
|
+
const next = await confirmed.json();
|
|
1000
|
+
setState(next);
|
|
1001
|
+
setDraft(toDraft(next.pairs));
|
|
1002
|
+
setActive(0);
|
|
892
1003
|
settingsChanged();
|
|
893
|
-
setNotice(tr(lang, "
|
|
1004
|
+
setNotice(tr(lang, "组合已保存。模型菜单中每个组合各占一项,不同会话可以同时使用不同组合。", "Combos saved. Each one is its own model-menu entry, so different conversations can use different combos at the same time."));
|
|
894
1005
|
} catch (error) {
|
|
895
1006
|
setError(error instanceof Error ? error.message : String(error));
|
|
896
1007
|
} finally {
|
|
897
1008
|
setSaving(false);
|
|
898
1009
|
}
|
|
899
1010
|
};
|
|
1011
|
+
const dirty = state ? JSON.stringify(draft.map(({ id, name, pair }) => ({
|
|
1012
|
+
id,
|
|
1013
|
+
name,
|
|
1014
|
+
pair
|
|
1015
|
+
}))) !== JSON.stringify(state.pairs.map(({ id, name, pair }) => ({
|
|
1016
|
+
id,
|
|
1017
|
+
name,
|
|
1018
|
+
pair
|
|
1019
|
+
}))) : false;
|
|
1020
|
+
const blocked = state?.pairs.filter((pair) => !pair.authorized) ?? [];
|
|
900
1021
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
901
1022
|
className: "fusion-page",
|
|
902
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: styles$
|
|
1023
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: styles$2 }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
903
1024
|
className: "fusion-settings",
|
|
904
1025
|
children: [
|
|
905
1026
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
906
1027
|
"data-window-drag": true,
|
|
907
1028
|
children: [
|
|
908
1029
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: "Fusion" }),
|
|
909
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "
|
|
1030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "每个组合是一对 Lead 和 Sidekick,会作为单独的一项出现在模型菜单里。开启多个会话时,可以为每个会话选择不同的组合。", "Each combo is a Lead and a Sidekick pair, listed as its own entry in the model menu. With several conversations open, each one can use a different combo.") }),
|
|
1031
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "Lead 处理日常请求、安排工作并审查结果;Sidekick 执行委派的任务。", "The Lead handles requests, plans and reviews; the Sidekick does the delegated work.") }),
|
|
910
1032
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
911
1033
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: tr(lang, "搭配建议", "Pairing tip") }),
|
|
912
1034
|
": ",
|
|
@@ -923,13 +1045,84 @@ window.__ModuleLoader__.load({
|
|
|
923
1045
|
children: tr(lang, "正在读取模型…", "Loading models…")
|
|
924
1046
|
}),
|
|
925
1047
|
state && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
926
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1049
|
+
className: "fusion-card",
|
|
1050
|
+
"aria-label": tr(lang, "组合列表", "Combo list"),
|
|
1051
|
+
children: [
|
|
1052
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1053
|
+
className: "fusion-combos",
|
|
1054
|
+
children: [draft.map((combo, index) => {
|
|
1055
|
+
const selected = combo.key === current?.key;
|
|
1056
|
+
const label = combo.name.trim() || tr(lang, "未命名组合", "Untitled combo");
|
|
1057
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1058
|
+
className: "fusion-combo",
|
|
1059
|
+
"data-active": selected,
|
|
1060
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1061
|
+
type: "button",
|
|
1062
|
+
className: "fusion-combo-main",
|
|
1063
|
+
"aria-pressed": selected,
|
|
1064
|
+
onClick: () => setActive(index),
|
|
1065
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1066
|
+
className: "fusion-combo-check",
|
|
1067
|
+
"aria-hidden": "true"
|
|
1068
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1069
|
+
className: "fusion-combo-text",
|
|
1070
|
+
children: [
|
|
1071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1072
|
+
className: "fusion-combo-name",
|
|
1073
|
+
children: label
|
|
1074
|
+
}),
|
|
1075
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1076
|
+
className: "fusion-combo-routes",
|
|
1077
|
+
children: `${routeName(combo.pair.lead)} → ${routeName(combo.pair.worker)}`
|
|
1078
|
+
}),
|
|
1079
|
+
missingRoutes(combo.pair, state.catalog).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1080
|
+
className: "fusion-combo-stale",
|
|
1081
|
+
children: tr(lang, "有模型已不可用,需要重新选择", "A model is no longer available; choose it again")
|
|
1082
|
+
})
|
|
1083
|
+
]
|
|
1084
|
+
})]
|
|
1085
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1086
|
+
type: "button",
|
|
1087
|
+
className: "fusion-combo-actions",
|
|
1088
|
+
disabled: saving || draft.length <= 1,
|
|
1089
|
+
"aria-label": tr(lang, `删除组合 ${label}`, `Delete combo ${label}`),
|
|
1090
|
+
onClick: (event) => {
|
|
1091
|
+
event.stopPropagation();
|
|
1092
|
+
removeCombo(index);
|
|
1093
|
+
},
|
|
1094
|
+
children: tr(lang, "删除", "Delete")
|
|
1095
|
+
})]
|
|
1096
|
+
}, combo.key);
|
|
1097
|
+
}), !draft.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "还没有组合。新增一个,选择 Lead 和 Sidekick 后保存。", "No combos yet. Add one, choose a Lead and a Sidekick, then save.") })]
|
|
1098
|
+
}),
|
|
1099
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1100
|
+
type: "button",
|
|
1101
|
+
className: "fusion-combo-add",
|
|
1102
|
+
disabled: saving || draft.length >= state.maxPairs,
|
|
1103
|
+
onClick: addCombo,
|
|
1104
|
+
children: tr(lang, "+ 新增组合", "+ Add combo")
|
|
1105
|
+
}),
|
|
1106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, `最多保存 ${state.maxPairs} 个组合;模型菜单中的名称是「Fusion · 组合名称」。`, `Up to ${state.maxPairs} combos; the model menu shows them as “Fusion · name”.`) })
|
|
1107
|
+
]
|
|
1108
|
+
}),
|
|
1109
|
+
current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
927
1110
|
className: "fusion-card",
|
|
928
1111
|
disabled: saving,
|
|
929
1112
|
children: [
|
|
1113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [tr(lang, "名称", "Name"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1114
|
+
type: "text",
|
|
1115
|
+
maxLength: 40,
|
|
1116
|
+
"aria-label": tr(lang, "组合名称", "Combo name"),
|
|
1117
|
+
value: current.name,
|
|
1118
|
+
onChange: (event) => update((combo) => ({
|
|
1119
|
+
...combo,
|
|
1120
|
+
name: event.target.value
|
|
1121
|
+
}))
|
|
1122
|
+
})] }),
|
|
930
1123
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelChoice, {
|
|
931
1124
|
role: "Lead",
|
|
932
|
-
route:
|
|
1125
|
+
route: current.pair.lead,
|
|
933
1126
|
catalog: state.catalog,
|
|
934
1127
|
onChange: (route) => {
|
|
935
1128
|
if (route) chooseModel("lead", route);
|
|
@@ -937,7 +1130,7 @@ window.__ModuleLoader__.load({
|
|
|
937
1130
|
}),
|
|
938
1131
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelChoice, {
|
|
939
1132
|
role: "Sidekick",
|
|
940
|
-
route:
|
|
1133
|
+
route: current.pair.worker,
|
|
941
1134
|
catalog: state.catalog,
|
|
942
1135
|
onChange: (route) => {
|
|
943
1136
|
if (route) chooseModel("worker", route);
|
|
@@ -949,37 +1142,42 @@ window.__ModuleLoader__.load({
|
|
|
949
1142
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "默认由当前角色模型压缩。也可以单独选择已连接的模型;请求由所选账号计费。", "By default each role compacts with its own model. You can pick another connected model; its account is billed.") }),
|
|
950
1143
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelChoice, {
|
|
951
1144
|
role: "compactor",
|
|
952
|
-
route:
|
|
1145
|
+
route: current.pair.compactor?.route,
|
|
953
1146
|
catalog: state.catalog,
|
|
954
|
-
onChange: (route) =>
|
|
955
|
-
const { compactor, ...rest } =
|
|
956
|
-
return
|
|
957
|
-
...
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1147
|
+
onChange: (route) => update((combo) => {
|
|
1148
|
+
const { compactor, ...rest } = combo.pair;
|
|
1149
|
+
return {
|
|
1150
|
+
...combo,
|
|
1151
|
+
pair: route ? {
|
|
1152
|
+
...rest,
|
|
1153
|
+
compactor: {
|
|
1154
|
+
route,
|
|
1155
|
+
maxOutputTokens: compactor?.maxOutputTokens ?? 8192
|
|
1156
|
+
}
|
|
1157
|
+
} : rest
|
|
1158
|
+
};
|
|
963
1159
|
})
|
|
964
1160
|
}),
|
|
965
|
-
|
|
1161
|
+
current.pair.compactor && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [tr(lang, "输出 Token 上限", "Output token limit"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
966
1162
|
"aria-label": tr(lang, "压缩输出 Token 上限", "Compaction output token limit"),
|
|
967
1163
|
type: "number",
|
|
968
1164
|
min: "1",
|
|
969
1165
|
max: "128000",
|
|
970
|
-
value:
|
|
1166
|
+
value: current.pair.compactor.maxOutputTokens,
|
|
971
1167
|
onChange: (event) => {
|
|
972
1168
|
const maxOutputTokens = Number(event.target.value);
|
|
973
|
-
|
|
974
|
-
...
|
|
975
|
-
|
|
976
|
-
...
|
|
977
|
-
|
|
1169
|
+
update((combo) => combo.pair.compactor ? {
|
|
1170
|
+
...combo,
|
|
1171
|
+
pair: {
|
|
1172
|
+
...combo.pair,
|
|
1173
|
+
compactor: {
|
|
1174
|
+
...combo.pair.compactor,
|
|
1175
|
+
maxOutputTokens
|
|
1176
|
+
}
|
|
978
1177
|
}
|
|
979
|
-
} :
|
|
1178
|
+
} : combo);
|
|
980
1179
|
}
|
|
981
|
-
})] })
|
|
982
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "已选中的会话仍使用原配置。", "Conversations already using Fusion keep their setup.") })
|
|
1180
|
+
})] })
|
|
983
1181
|
]
|
|
984
1182
|
})] })
|
|
985
1183
|
]
|
|
@@ -987,11 +1185,11 @@ window.__ModuleLoader__.load({
|
|
|
987
1185
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
988
1186
|
className: "fusion-actions",
|
|
989
1187
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "已选中的会话保留原组合;新选择使用新组合。额度耗尽后要换当前任务的 Sidekick,用该会话的 Fusion 恢复面板,而不是只改这里。", "Conversations already using Fusion keep their pair; new selections use the new one. After a quota stop, switch this conversation's Sidekick from its Fusion recovery panel, not only here.") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
990
|
-
disabled: saving || !
|
|
1188
|
+
disabled: saving || !dirty || !draft.length,
|
|
991
1189
|
onClick: () => {
|
|
992
1190
|
save();
|
|
993
1191
|
},
|
|
994
|
-
children: saving ? tr(lang, "正在保存…", "Saving…") : tr(lang, "保存组合", "Save
|
|
1192
|
+
children: saving ? tr(lang, "正在保存…", "Saving…") : tr(lang, "保存组合", "Save combos")
|
|
995
1193
|
})]
|
|
996
1194
|
}),
|
|
997
1195
|
notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1000,10 +1198,10 @@ window.__ModuleLoader__.load({
|
|
|
1000
1198
|
}),
|
|
1001
1199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "输出参数跟随原生模型设置,Fusion 不额外限制请求次数或任务轮数。", "Output settings follow the native model settings; Fusion adds no request or round limits.") }),
|
|
1002
1200
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "保存即表示允许 Fusion 使用所选模型的现有账号;费用由这些账号计入,当前未提供可靠的实际账单金额。", "Saving lets Fusion use the chosen models' existing accounts; they are billed there, and no reliable billed amount is available here.") }),
|
|
1003
|
-
|
|
1201
|
+
blocked.map((pair) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1004
1202
|
role: "status",
|
|
1005
|
-
children:
|
|
1006
|
-
}),
|
|
1203
|
+
children: `${pair.name}: ${tr(lang, "尚未就绪,需要重新保存这个组合", "not ready yet; save this combo again")}${pair.reason ? `: ${pair.reason}` : ""}`
|
|
1204
|
+
}, pair.id)),
|
|
1007
1205
|
state.managedAuthorization && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: tr(lang, "运行额度由外部授权配置管理。", "Run limits are managed by an external authorization file.") })
|
|
1008
1206
|
] }),
|
|
1009
1207
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FusionCache, { refresh: state?.revision ?? 0 }),
|
|
@@ -1333,22 +1531,28 @@ window.__ModuleLoader__.load({
|
|
|
1333
1531
|
document.removeEventListener("visibilitychange", own.visibility);
|
|
1334
1532
|
};
|
|
1335
1533
|
}
|
|
1336
|
-
/**
|
|
1337
|
-
|
|
1338
|
-
|
|
1534
|
+
/**
|
|
1535
|
+
* One in-flight read per mounted session; collapsed hints still discover new attention states.
|
|
1536
|
+
* `live` asks for the fast cadence whenever the task is running, for a view whose whole
|
|
1537
|
+
* purpose is the current stage (the role and permission of the model working now).
|
|
1538
|
+
*/
|
|
1539
|
+
function useFusionStatus(sessionId, expandedTask, attentionOnly = false, live = false) {
|
|
1540
|
+
const expandedRef = (0, react.useRef)(expandedTask), attentionRef = (0, react.useRef)(attentionOnly), liveRef = (0, react.useRef)(live);
|
|
1339
1541
|
expandedRef.current = expandedTask;
|
|
1340
1542
|
attentionRef.current = attentionOnly;
|
|
1543
|
+
liveRef.current = live;
|
|
1341
1544
|
(0, react.useEffect)(() => {
|
|
1342
1545
|
if (expandedTask) entries.get(sessionId)?.visibility();
|
|
1343
1546
|
}, [sessionId, expandedTask]);
|
|
1344
1547
|
return (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => subscribe(sessionId, listener, () => {
|
|
1548
|
+
if (liveRef.current) return true;
|
|
1345
1549
|
const task = entries.get(sessionId)?.snapshot?.status?.task;
|
|
1346
1550
|
return expandedRef.current !== void 0 && task?.id === expandedRef.current && (!attentionRef.current || task.attention);
|
|
1347
1551
|
}), [sessionId]), (0, react.useCallback)(() => entries.get(sessionId)?.snapshot, [sessionId]), () => void 0);
|
|
1348
1552
|
}
|
|
1349
1553
|
//#endregion
|
|
1350
1554
|
//#region src/client/FusionStatus.tsx
|
|
1351
|
-
const styles = `
|
|
1555
|
+
const styles$1 = `
|
|
1352
1556
|
.fusion-status{margin:8px 12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;overflow-wrap:anywhere}
|
|
1353
1557
|
.fusion-status>summary{cursor:pointer;min-height:44px;display:flex;align-items:center;gap:12px;list-style:none;border-top:1px solid var(--dsw-alias-border-l2);outline-offset:2px}
|
|
1354
1558
|
.fusion-status>summary::before{content:'›';font-size:18px;flex:none}.fusion-status[open]>summary::before{content:'⌄'}
|
|
@@ -1428,7 +1632,7 @@ window.__ModuleLoader__.load({
|
|
|
1428
1632
|
className: "fusion-status",
|
|
1429
1633
|
onToggle: (event) => setExpandedTask(event.currentTarget.open ? task.id : void 0),
|
|
1430
1634
|
children: [
|
|
1431
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: styles }),
|
|
1635
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: styles$1 }),
|
|
1432
1636
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
1433
1637
|
"aria-label": tr(lang, `Fusion · ${stage},任务详情`, `Fusion · ${stage}, task details`),
|
|
1434
1638
|
"data-ud-check": "fusion-status-summary",
|
|
@@ -1572,7 +1776,12 @@ window.__ModuleLoader__.load({
|
|
|
1572
1776
|
if (!response.ok) throw new Error("unavailable");
|
|
1573
1777
|
return response.json();
|
|
1574
1778
|
}).then((state) => {
|
|
1575
|
-
if (active)
|
|
1779
|
+
if (!active) return;
|
|
1780
|
+
const combo = state.pairs.find((pair) => pair.id === selected?.model);
|
|
1781
|
+
if (!state.pairs.length) setMessage(tr(lang, "Fusion 尚未配置模型,请前往设置 → Fusion 选择 Lead 和 Sidekick。", "Fusion has no models yet: open Settings → Fusion and choose a Lead and a Sidekick."));
|
|
1782
|
+
else if (!combo) setMessage(tr(lang, "当前组合已被删除,请在模型菜单中选择其他组合。", "This combo was removed; pick another one in the model menu."));
|
|
1783
|
+
else if (!combo.authorized) setMessage(tr(lang, "Fusion 当前配置未就绪,请前往设置 → Fusion 查看。", "The Fusion setup is not ready: see Settings → Fusion."));
|
|
1784
|
+
else setMessage(void 0);
|
|
1576
1785
|
}).catch(() => {
|
|
1577
1786
|
if (active) setMessage(tr(lang, "暂时无法确认 Fusion 配置,请前往设置 → Fusion 查看。", "Cannot confirm the Fusion setup right now: see Settings → Fusion."));
|
|
1578
1787
|
});
|
|
@@ -1584,7 +1793,11 @@ window.__ModuleLoader__.load({
|
|
|
1584
1793
|
abort.abort();
|
|
1585
1794
|
off();
|
|
1586
1795
|
};
|
|
1587
|
-
}, [
|
|
1796
|
+
}, [
|
|
1797
|
+
fusion,
|
|
1798
|
+
lang,
|
|
1799
|
+
selected?.model
|
|
1800
|
+
]);
|
|
1588
1801
|
if (!fusion) return null;
|
|
1589
1802
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [message && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1590
1803
|
role: "status",
|
|
@@ -1601,6 +1814,54 @@ window.__ModuleLoader__.load({
|
|
|
1601
1814
|
})] });
|
|
1602
1815
|
}
|
|
1603
1816
|
//#endregion
|
|
1817
|
+
//#region src/client/FusionPermission.tsx
|
|
1818
|
+
const styles = `
|
|
1819
|
+
.fusion-permission{display:inline-flex;align-items:center;gap:4px;height:28px;min-width:0;max-width:220px;padding:0 6px;border-radius:var(--dsw-radius-sm);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;font-weight:500}
|
|
1820
|
+
.fusion-permission svg{width:14px;height:14px;flex:none}
|
|
1821
|
+
.fusion-permission .fusion-permission-role{flex:none;color:var(--dsw-alias-label-caption)}
|
|
1822
|
+
.fusion-permission .fusion-permission-mode{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1823
|
+
`;
|
|
1824
|
+
const modeOf = (lang, mode) => mode === "read-only" ? tr(lang, "仅可查看", "Read Only") : mode === "workspace-write" ? tr(lang, "工作区内修改", "Workspace Write") : tr(lang, "完全权限", "Full access");
|
|
1825
|
+
const iconOf = (mode) => mode === "read-only" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.PermissionIconReadOnlyRegular, {}) : mode === "workspace-write" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.PermissionIconWorkspaceWriteRegular, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.PermissionIconFullAccessRegular, {});
|
|
1826
|
+
/**
|
|
1827
|
+
* The permission of the model working now. The composer's native permission control
|
|
1828
|
+
* belongs to the conversation (Lead) session, which Fusion confines to read-only, and
|
|
1829
|
+
* that confinement matches no configured preset — so the control reads Custom while a
|
|
1830
|
+
* task runs. This additive chip names the role and its mode, so the tool row never
|
|
1831
|
+
* shows an unexplained Custom alone.
|
|
1832
|
+
*/
|
|
1833
|
+
function FusionPermission({ sessionId, directory, load }) {
|
|
1834
|
+
const selected = (0, react.useSyncExternalStore)(directory.subscribe, directory.getSnapshot).current;
|
|
1835
|
+
const fusion = isFusionSelection(selected ?? void 0);
|
|
1836
|
+
const lang = useLang();
|
|
1837
|
+
const snapshot = useFusionStatus(sessionId, void 0, false, true);
|
|
1838
|
+
(0, react.useEffect)(() => {
|
|
1839
|
+
load();
|
|
1840
|
+
}, [load]);
|
|
1841
|
+
const permissions = fusion ? snapshot?.status?.task?.permissions : void 0;
|
|
1842
|
+
if (!permissions) return null;
|
|
1843
|
+
const role = permissions.active === "worker" ? "Sidekick" : "Lead";
|
|
1844
|
+
const mode = permissions.active === "worker" ? permissions.worker : permissions.lead;
|
|
1845
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1846
|
+
className: "fusion-permission",
|
|
1847
|
+
title: tr(lang, `Fusion 按角色分离权限:Lead ${modeOf(lang, permissions.lead)},Sidekick ${modeOf(lang, permissions.worker)}。输入框的权限控件属于对话会话(Lead),运行期间可能显示 Custom。`, `Fusion separates permissions by role: Lead ${modeOf(lang, permissions.lead)}, Sidekick ${modeOf(lang, permissions.worker)}. The permission control belongs to the conversation (Lead) session, so it may read Custom while a task runs.`),
|
|
1848
|
+
"aria-label": `${role} · ${modeOf(lang, mode)}`,
|
|
1849
|
+
"data-ud-check": "fusion-permission",
|
|
1850
|
+
children: [
|
|
1851
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: styles }),
|
|
1852
|
+
iconOf(mode),
|
|
1853
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1854
|
+
className: "fusion-permission-role",
|
|
1855
|
+
children: role
|
|
1856
|
+
}),
|
|
1857
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1858
|
+
className: "fusion-permission-mode",
|
|
1859
|
+
children: modeOf(lang, mode)
|
|
1860
|
+
})
|
|
1861
|
+
]
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
//#endregion
|
|
1604
1865
|
//#region src/activity.ts
|
|
1605
1866
|
/** Namespaced display identity; the unchanged native identity remains in source. */
|
|
1606
1867
|
function activityCallId(activity) {
|
|
@@ -1890,21 +2151,28 @@ window.__ModuleLoader__.load({
|
|
|
1890
2151
|
label: () => "Fusion"
|
|
1891
2152
|
}, FusionPanelIcon));
|
|
1892
2153
|
ctx.inject(["modelDirectories"], (scope) => {
|
|
2154
|
+
const sessionProps = (sessionId) => {
|
|
2155
|
+
const directory = scope.modelDirectories.directoryFor(sessionId);
|
|
2156
|
+
return {
|
|
2157
|
+
sessionId,
|
|
2158
|
+
directory: directory.store,
|
|
2159
|
+
load: () => {
|
|
2160
|
+
directory.load().catch(() => void 0);
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
};
|
|
1893
2164
|
scope.slots.inject("conversation.input.dock", () => scope.slots.register({
|
|
1894
2165
|
name: "conversation.input.dock",
|
|
1895
2166
|
id: "fusion-configuration",
|
|
1896
2167
|
order: 20,
|
|
1897
|
-
inject:
|
|
1898
|
-
const directory = scope.modelDirectories.directoryFor(sessionId);
|
|
1899
|
-
return {
|
|
1900
|
-
sessionId,
|
|
1901
|
-
directory: directory.store,
|
|
1902
|
-
load: () => {
|
|
1903
|
-
directory.load().catch(() => void 0);
|
|
1904
|
-
}
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
2168
|
+
inject: sessionProps
|
|
1907
2169
|
}, FusionHint));
|
|
2170
|
+
scope.slots.inject("conversation.input.left", () => scope.slots.register({
|
|
2171
|
+
name: "conversation.input.left",
|
|
2172
|
+
id: "fusion-permission",
|
|
2173
|
+
order: 10,
|
|
2174
|
+
inject: sessionProps
|
|
2175
|
+
}, FusionPermission));
|
|
1908
2176
|
});
|
|
1909
2177
|
}
|
|
1910
2178
|
//#endregion
|