dsh-dock 0.13.0 → 0.14.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/client.js CHANGED
@@ -2187,7 +2187,7 @@ function initFeatureState(defs) {
2187
2187
  }
2188
2188
  function syncFeatureStateFromHost(defs) {
2189
2189
  if (typeof fetch !== "function") return;
2190
- fetch("/dsh-dock/features").then(async (res) => {
2190
+ fetch("dsh-dock/features").then(async (res) => {
2191
2191
  if (!res.ok) return;
2192
2192
  const data = await res.json().catch(() => null);
2193
2193
  const persisted = data && data.ok && data.data && data.data.persisted;
@@ -2214,7 +2214,7 @@ function syncFeatureStateFromHost(defs) {
2214
2214
  }
2215
2215
  function pushFeatureEnabledToHost(id, enabled) {
2216
2216
  if (typeof fetch !== "function") return;
2217
- fetch("/dsh-dock/features", {
2217
+ fetch("dsh-dock/features", {
2218
2218
  method: "POST",
2219
2219
  headers: { "content-type": "application/json" },
2220
2220
  body: JSON.stringify({ id, enabled })
@@ -2300,7 +2300,7 @@ function subscribePanel(fn) {
2300
2300
  var import_react2 = __toESM(require("react"), 1);
2301
2301
  var import_jsx_runtime = require("react/jsx-runtime");
2302
2302
  function rpcCall(method, args) {
2303
- return fetch("/dsh-dock/tokenlog/" + method, {
2303
+ return fetch("dsh-dock/tokenlog/" + method, {
2304
2304
  method: "POST",
2305
2305
  headers: { "content-type": "application/json" },
2306
2306
  body: JSON.stringify(args === void 0 ? {} : args)
@@ -3731,7 +3731,7 @@ var modelsStore = {
3731
3731
  load() {
3732
3732
  if (this.snap.loading) return;
3733
3733
  this.set({ loading: true, error: null });
3734
- fetch("/dsh-dock/models", { cache: "no-store", signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u6A21\u578B\u76EE\u5F55\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
3734
+ fetch("dsh-dock/models", { cache: "no-store", signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u6A21\u578B\u76EE\u5F55\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
3735
3735
  }
3736
3736
  };
3737
3737
  function useModels() {
@@ -3748,35 +3748,190 @@ var DKM_TAGS = [["video", "\u89C6\u9891"], ["audio", "\u97F3\u9891"], ["document
3748
3748
  var DKM_PI_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
3749
3749
  var DKM_DS_LEVELS = ["off", "low", "high", "max"];
3750
3750
  var DKM_EFFORT_NAMES = { off: "\u5173\u95ED", minimal: "\u6781\u4F4E", low: "\u4F4E", medium: "\u4E2D", high: "\u9AD8", xhigh: "\u8D85\u9AD8", max: "\u6700\u9AD8" };
3751
+ var DKM_APIS = [
3752
+ ["openai-completions", "OpenAI \u517C\u5BB9\uFF08chat/completions\uFF09"],
3753
+ ["openai-responses", "OpenAI Responses"],
3754
+ ["anthropic-messages", "Anthropic Messages"]
3755
+ ];
3756
+ var DKM_ROUTE_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3757
+ function dkmEnvFromRoute(route) {
3758
+ const s = String(route || "").toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
3759
+ if (!s) return "";
3760
+ return (/^[0-9]/.test(s) ? "KEY_" + s : s) + "_API_KEY";
3761
+ }
3762
+ var DKM_ALL_INPUT = DKM_MODALITIES.map((x) => x[0]);
3763
+ var DKM_ALL_TAGS = DKM_TAGS.map((x) => x[0]);
3764
+ function dkmToggleIn(list, v) {
3765
+ return list.indexOf(v) >= 0 ? list.filter((x) => x !== v) : list.concat([v]);
3766
+ }
3767
+ function dkmAllInputs(on) {
3768
+ return { input: on ? DKM_ALL_INPUT.slice() : [], tags: on ? DKM_ALL_TAGS.slice() : [] };
3769
+ }
3770
+ function dkmAllLevels(on) {
3771
+ const lv = {};
3772
+ for (const k of DKM_PI_LEVELS) lv[k] = !!on;
3773
+ return { effortsMode: "custom", effortLevels: lv };
3774
+ }
3775
+ function draftFromModel(m) {
3776
+ const levels = {};
3777
+ if (m.efforts === "custom" && m.effortMap && typeof m.effortMap === "object") {
3778
+ for (const lv of DKM_PI_LEVELS) levels[lv] = m.effortMap[lv] !== void 0;
3779
+ }
3780
+ return {
3781
+ id: m.id || "",
3782
+ name: m.name || "",
3783
+ contextWindow: m.contextWindow != null ? String(m.contextWindow) : "",
3784
+ maxTokens: m.maxTokens != null ? String(m.maxTokens) : "",
3785
+ input: m.input && m.input.length ? m.input.slice() : ["text"],
3786
+ tags: (m.tags || []).slice(),
3787
+ effortsMode: m.efforts === "custom" ? "custom" : m.efforts === "off" ? "off" : "inherit",
3788
+ effortLevels: levels,
3789
+ raw: m.raw && typeof m.raw === "object" ? m.raw : null
3790
+ };
3791
+ }
3792
+ function emptyModelDraft() {
3793
+ return { id: "", name: "", contextWindow: "", maxTokens: "", input: ["text"], tags: [], effortsMode: "inherit", effortLevels: {}, raw: null };
3794
+ }
3795
+ function emptyProviderForm() {
3796
+ return {
3797
+ route: "",
3798
+ displayName: "",
3799
+ api: "openai-completions",
3800
+ baseURL: "",
3801
+ apiKeyEnv: "",
3802
+ apiKey: "",
3803
+ defaultInput: ["text"],
3804
+ models: [],
3805
+ extra: {}
3806
+ };
3807
+ }
3808
+ function providerFormFromProvider(p) {
3809
+ const raw = p.rawProfile && typeof p.rawProfile === "object" && !Array.isArray(p.rawProfile) ? p.rawProfile : {};
3810
+ const extra = Object.assign({}, raw);
3811
+ delete extra.displayName;
3812
+ delete extra.api;
3813
+ delete extra.baseURL;
3814
+ delete extra.apiKeyEnv;
3815
+ delete extra.defaultInput;
3816
+ delete extra.models;
3817
+ const models = draftFromProvider(p).models;
3818
+ return {
3819
+ route: p.route || p.id || "",
3820
+ displayName: typeof raw.displayName === "string" ? raw.displayName : p.displayName || "",
3821
+ api: typeof raw.api === "string" && raw.api ? raw.api : p.api || "openai-completions",
3822
+ baseURL: typeof raw.baseURL === "string" ? raw.baseURL : p.baseURL || "",
3823
+ apiKeyEnv: typeof raw.apiKeyEnv === "string" ? raw.apiKeyEnv : p.apiKeyEnv || "",
3824
+ apiKey: "",
3825
+ defaultInput: Array.isArray(raw.defaultInput) && raw.defaultInput.length ? raw.defaultInput.slice() : Array.isArray(p.defaultInput) && p.defaultInput.length ? p.defaultInput.slice() : ["text"],
3826
+ models: models.length ? models : [emptyModelDraft()],
3827
+ extra
3828
+ };
3829
+ }
3751
3830
  function draftFromProvider(p) {
3752
3831
  return {
3753
3832
  id: p.id,
3754
3833
  kind: p.kind,
3755
3834
  thinking: p.thinking === "disabled" ? "disabled" : "enabled",
3756
3835
  defaultEffort: DKM_DS_LEVELS.indexOf(p.defaultEffort) >= 0 ? p.defaultEffort : "high",
3757
- models: (p.models || []).map((m) => {
3758
- const levels = {};
3759
- if (m.efforts === "custom" && m.effortMap && typeof m.effortMap === "object") {
3760
- for (const lv of DKM_PI_LEVELS) levels[lv] = m.effortMap[lv] !== void 0;
3761
- }
3762
- return {
3763
- id: m.id || "",
3764
- name: m.name || "",
3765
- contextWindow: m.contextWindow != null ? String(m.contextWindow) : "",
3766
- maxTokens: m.maxTokens != null ? String(m.maxTokens) : "",
3767
- input: m.input && m.input.length ? m.input.slice() : ["text"],
3768
- tags: (m.tags || []).slice(),
3769
- effortsMode: m.efforts === "custom" ? "custom" : m.efforts === "off" ? "off" : "inherit",
3770
- effortLevels: levels,
3771
- raw: m.raw && typeof m.raw === "object" ? m.raw : null
3772
- };
3773
- })
3836
+ models: (p.models || []).map(draftFromModel)
3774
3837
  };
3775
3838
  }
3776
- function ModelsView() {
3839
+ function ModelRow(props) {
3840
+ const m = props.m, i = props.i, kind = props.kind, onPatch = props.onPatch, onDel = props.onDel;
3841
+ return import_react3.default.createElement(
3842
+ "div",
3843
+ { key: i, className: "dkm-model" },
3844
+ import_react3.default.createElement(
3845
+ "div",
3846
+ { className: "dkm-model-head" },
3847
+ import_react3.default.createElement("span", { className: "dkm-model-idx" }, "#" + (i + 1)),
3848
+ import_react3.default.createElement("button", { type: "button", className: "dkm-del", onClick: () => onDel(i) }, "\u5220\u9664")
3849
+ ),
3850
+ import_react3.default.createElement(
3851
+ "div",
3852
+ { className: "dkm-grid" },
3853
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6A21\u578B id\uFF08\u5FC5\u586B\uFF0C\u5982 deepseek-v4-pro\uFF09", value: m.id, onChange: (e) => onPatch(i, { id: e.target.value }) }),
3854
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09", value: m.name, onChange: (e) => onPatch(i, { name: e.target.value }) }),
3855
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u4E0A\u4E0B\u6587\u7A97\u53E3", inputMode: "numeric", value: m.contextWindow, onChange: (e) => onPatch(i, { contextWindow: e.target.value }) }),
3856
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6700\u5927\u8F93\u51FA", inputMode: "numeric", value: m.maxTokens, onChange: (e) => onPatch(i, { maxTokens: e.target.value }) })
3857
+ ),
3858
+ import_react3.default.createElement(
3859
+ "div",
3860
+ { className: "dkm-checks" },
3861
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u8F93\u5165\u7C7B\u578B\uFF1A"),
3862
+ DKM_MODALITIES.map(([v, label]) => import_react3.default.createElement(
3863
+ "label",
3864
+ {
3865
+ key: v,
3866
+ className: "dkm-check",
3867
+ title: v === "image" ? "\u7531\u4F60\u58F0\u660E\u8BE5\u7AEF\u70B9\u652F\u6301\u56FE\u7247\uFF1A\u4FDD\u5B58\u540E\u539F\u56FE\u76F4\u53D1\uFF0C\u5E76\u53EF\u4F5C\u4E3A\u89C6\u89C9\u6A21\u578B\u5019\u9009\u3002\u7CFB\u7EDF\u4E0D\u4F1A\u66FF\u4F60\u731C\u6D4B\uFF1B\u7AEF\u70B9\u5B9E\u9645\u4E0D\u652F\u6301\u65F6\u53D6\u6D88\u52FE\u9009\u5373\u53EF\u6062\u590D\u8D70\u56FE\u7247\u7406\u89E3\u4EE3\u7406" : null
3868
+ },
3869
+ import_react3.default.createElement("input", {
3870
+ type: "checkbox",
3871
+ checked: m.input.indexOf(v) >= 0,
3872
+ onChange: () => {
3873
+ const next = dkmToggleIn(m.input, v);
3874
+ onPatch(i, { input: next.length > 0 ? next : ["text"] });
3875
+ }
3876
+ }),
3877
+ label
3878
+ )),
3879
+ import_react3.default.createElement("span", { className: "dkm-label dkm-tag", title: "\u6807\u6CE8\u4EC5\u5728\u672C\u9762\u677F\u5C55\u793A\u4E0E\u6301\u4E45\u5316\uFF0C\u4E0D\u53C2\u4E0E\u8BF7\u6C42\u8DEF\u7531\uFF08\u5B98\u65B9 schema \u53EA\u63A5\u53D7\u6587\u672C/\u56FE\u7247\uFF09" }, "\u6807\u6CE8\uFF1A"),
3880
+ DKM_TAGS.map(([v, label]) => import_react3.default.createElement(
3881
+ "label",
3882
+ { key: v, className: "dkm-check dkm-tag" },
3883
+ import_react3.default.createElement("input", { type: "checkbox", checked: m.tags.indexOf(v) >= 0, onChange: () => onPatch(i, { tags: dkmToggleIn(m.tags, v) }) }),
3884
+ label
3885
+ )),
3886
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => onPatch(i, dkmAllInputs(true)) }, "\u5168\u9009"),
3887
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u52FE\u9009\uFF08\u8F93\u5165\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => onPatch(i, dkmAllInputs(false)) }, "\u53D6\u6D88\u5168\u9009")
3888
+ ),
3889
+ import_react3.default.createElement(
3890
+ "div",
3891
+ { className: "dkm-efforts" },
3892
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u5F3A\u5EA6\uFF1A"),
3893
+ kind === "deepseek" ? import_react3.default.createElement("span", { className: "dkm-sub" }, "\u968F Provider \u8BBE\u7F6E\uFF08Off/Low/High/Max\uFF0C\u5168\u6A21\u578B\u5171\u4EAB\uFF09") : import_react3.default.createElement(
3894
+ "span",
3895
+ null,
3896
+ import_react3.default.createElement(
3897
+ "select",
3898
+ {
3899
+ className: "dkm-select",
3900
+ value: m.effortsMode,
3901
+ onChange: (e) => onPatch(i, {
3902
+ effortsMode: e.target.value,
3903
+ effortLevels: e.target.value === "custom" && m.effortsMode !== "custom" ? { off: true, low: true, high: true } : m.effortLevels
3904
+ })
3905
+ },
3906
+ import_react3.default.createElement("option", { value: "inherit" }, "\u8DDF\u968F\u76EE\u5F55\u9ED8\u8BA4"),
3907
+ import_react3.default.createElement("option", { value: "off" }, "\u4E0D\u652F\u6301\u601D\u8003"),
3908
+ import_react3.default.createElement("option", { value: "custom" }, "\u81EA\u5B9A\u4E49\u6863\u4F4D")
3909
+ ),
3910
+ m.effortsMode === "custom" ? DKM_PI_LEVELS.map((lv) => import_react3.default.createElement(
3911
+ "label",
3912
+ { key: lv, className: "dkm-check" },
3913
+ import_react3.default.createElement("input", {
3914
+ type: "checkbox",
3915
+ checked: !!m.effortLevels[lv],
3916
+ onChange: () => {
3917
+ const nl = Object.assign({}, m.effortLevels);
3918
+ nl[lv] = !nl[lv];
3919
+ onPatch(i, { effortLevels: nl });
3920
+ }
3921
+ }),
3922
+ DKM_EFFORT_NAMES[lv] || lv
3923
+ )) : null,
3924
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => onPatch(i, dkmAllLevels(true)) }, "\u5168\u9009"),
3925
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => onPatch(i, dkmAllLevels(false)) }, "\u53D6\u6D88\u5168\u9009")
3926
+ )
3927
+ )
3928
+ );
3929
+ }
3930
+ function ModelsView(props) {
3777
3931
  const snap = useModels();
3778
3932
  const data = snap.data;
3779
3933
  const providers = data && Array.isArray(data.providers) ? data.providers : [];
3934
+ const params = props && props.params;
3780
3935
  const [selId, setSelId] = import_react3.default.useState(null);
3781
3936
  const [draft, setDraft] = import_react3.default.useState(null);
3782
3937
  const [builtKey, setBuiltKey] = import_react3.default.useState(null);
@@ -3789,6 +3944,8 @@ function ModelsView() {
3789
3944
  const [vpSaving, setVpSaving] = import_react3.default.useState(false);
3790
3945
  const [vpMsg, setVpMsg] = import_react3.default.useState(null);
3791
3946
  const [vpDirectExpanded, setVpDirectExpanded] = import_react3.default.useState(false);
3947
+ const [page, setPage] = import_react3.default.useState("settings");
3948
+ const [pageOpenCreate, setPageOpenCreate] = import_react3.default.useState(false);
3792
3949
  import_react3.default.useEffect(() => {
3793
3950
  modelsStore.load();
3794
3951
  }, []);
@@ -3813,7 +3970,7 @@ function ModelsView() {
3813
3970
  const saveVp = () => {
3814
3971
  if (!vpDraft || vpSaving) return;
3815
3972
  setVpSaving(true);
3816
- fetch("/dsh-dock/models", {
3973
+ fetch("dsh-dock/models", {
3817
3974
  method: "POST",
3818
3975
  headers: { "content-type": "application/json" },
3819
3976
  body: JSON.stringify({ visionProxy: vpDraft, revisions: data && data.revisions || {} }),
@@ -3851,6 +4008,13 @@ function ModelsView() {
3851
4008
  setSelId(id);
3852
4009
  setMsg(null);
3853
4010
  };
4011
+ import_react3.default.useEffect(() => {
4012
+ if (params && params.page === "providers") setPage("providers");
4013
+ if (params && params.openProviderForm) {
4014
+ setPage("providers");
4015
+ setPageOpenCreate(true);
4016
+ }
4017
+ }, []);
3854
4018
  const save = () => {
3855
4019
  if (!draft || !cur || !dirty || saving) return;
3856
4020
  const models = draft.models.map((m) => ({
@@ -3870,7 +4034,7 @@ function ModelsView() {
3870
4034
  payload.defaultEffort = draft.defaultEffort;
3871
4035
  }
3872
4036
  setSaving(true);
3873
- fetch("/dsh-dock/models", {
4037
+ fetch("dsh-dock/models", {
3874
4038
  method: "POST",
3875
4039
  headers: { "content-type": "application/json" },
3876
4040
  body: JSON.stringify(payload),
@@ -3884,98 +4048,28 @@ function ModelsView() {
3884
4048
  }
3885
4049
  }).catch((e) => setMsg({ ok: false, text: e && e.message || String(e) })).then(() => setSaving(false));
3886
4050
  };
3887
- const modelRow = (m, i) => import_react3.default.createElement(
3888
- "div",
3889
- { key: i, className: "dkm-model" },
3890
- import_react3.default.createElement(
3891
- "div",
3892
- { className: "dkm-model-head" },
3893
- import_react3.default.createElement("span", { className: "dkm-model-idx" }, "#" + (i + 1)),
3894
- import_react3.default.createElement("button", {
3895
- type: "button",
3896
- className: "dkm-del",
3897
- onClick: () => patchDraft({ models: draft.models.filter((_, j) => j !== i) })
3898
- }, "\u5220\u9664")
3899
- ),
3900
- import_react3.default.createElement(
3901
- "div",
3902
- { className: "dkm-grid" },
3903
- import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6A21\u578B id\uFF08\u5FC5\u586B\uFF0C\u5982 deepseek-v4-pro\uFF09", value: m.id, onChange: (e) => patchModel(i, { id: e.target.value }) }),
3904
- import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09", value: m.name, onChange: (e) => patchModel(i, { name: e.target.value }) }),
3905
- import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u4E0A\u4E0B\u6587\u7A97\u53E3", inputMode: "numeric", value: m.contextWindow, onChange: (e) => patchModel(i, { contextWindow: e.target.value }) }),
3906
- import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6700\u5927\u8F93\u51FA", inputMode: "numeric", value: m.maxTokens, onChange: (e) => patchModel(i, { maxTokens: e.target.value }) })
3907
- ),
3908
- import_react3.default.createElement(
3909
- "div",
3910
- { className: "dkm-checks" },
3911
- import_react3.default.createElement("span", { className: "dkm-label" }, "\u8F93\u5165\u7C7B\u578B\uFF1A"),
3912
- DKM_MODALITIES.map(([v, label]) => import_react3.default.createElement(
3913
- "label",
3914
- {
3915
- key: v,
3916
- className: "dkm-check",
3917
- title: v === "image" ? "\u7531\u4F60\u58F0\u660E\u8BE5\u7AEF\u70B9\u652F\u6301\u56FE\u7247\uFF1A\u4FDD\u5B58\u540E\u539F\u56FE\u76F4\u53D1\uFF0C\u5E76\u53EF\u4F5C\u4E3A\u89C6\u89C9\u6A21\u578B\u5019\u9009\u3002\u7CFB\u7EDF\u4E0D\u4F1A\u66FF\u4F60\u731C\u6D4B\uFF1B\u7AEF\u70B9\u5B9E\u9645\u4E0D\u652F\u6301\u65F6\u53D6\u6D88\u52FE\u9009\u5373\u53EF\u6062\u590D\u8D70\u56FE\u7247\u7406\u89E3\u4EE3\u7406" : null
3918
- },
3919
- import_react3.default.createElement("input", {
3920
- type: "checkbox",
3921
- checked: m.input.indexOf(v) >= 0,
3922
- onChange: () => {
3923
- const next = toggleIn(m.input, v);
3924
- patchModel(i, { input: next.length > 0 ? next : ["text"] });
3925
- }
3926
- }),
3927
- label
3928
- )),
3929
- import_react3.default.createElement("span", { className: "dkm-label dkm-tag", title: "\u6807\u6CE8\u4EC5\u5728\u672C\u9762\u677F\u5C55\u793A\u4E0E\u6301\u4E45\u5316\uFF0C\u4E0D\u53C2\u4E0E\u8BF7\u6C42\u8DEF\u7531\uFF08\u5B98\u65B9 schema \u53EA\u63A5\u53D7\u6587\u672C/\u56FE\u7247\uFF09" }, "\u6807\u6CE8\uFF1A"),
3930
- DKM_TAGS.map(([v, label]) => import_react3.default.createElement(
3931
- "label",
3932
- { key: v, className: "dkm-check dkm-tag" },
3933
- import_react3.default.createElement("input", { type: "checkbox", checked: m.tags.indexOf(v) >= 0, onChange: () => patchModel(i, { tags: toggleIn(m.tags, v) }) }),
3934
- label
3935
- )),
3936
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => patchModel(i, allInputs(true)) }, "\u5168\u9009"),
3937
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u52FE\u9009\uFF08\u8F93\u5165\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => patchModel(i, allInputs(false)) }, "\u53D6\u6D88\u5168\u9009")
3938
- ),
3939
- import_react3.default.createElement(
3940
- "div",
3941
- { className: "dkm-efforts" },
3942
- import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u5F3A\u5EA6\uFF1A"),
3943
- cur.kind === "deepseek" ? import_react3.default.createElement("span", { className: "dkm-sub" }, "\u968F Provider \u8BBE\u7F6E\uFF08Off/Low/High/Max\uFF0C\u5168\u6A21\u578B\u5171\u4EAB\uFF09") : import_react3.default.createElement(
3944
- "span",
3945
- null,
3946
- import_react3.default.createElement(
3947
- "select",
3948
- {
3949
- className: "dkm-select",
3950
- value: m.effortsMode,
3951
- onChange: (e) => patchModel(i, {
3952
- effortsMode: e.target.value,
3953
- effortLevels: e.target.value === "custom" && m.effortsMode !== "custom" ? { off: true, low: true, high: true } : m.effortLevels
3954
- })
3955
- },
3956
- import_react3.default.createElement("option", { value: "inherit" }, "\u8DDF\u968F\u76EE\u5F55\u9ED8\u8BA4"),
3957
- import_react3.default.createElement("option", { value: "off" }, "\u4E0D\u652F\u6301\u601D\u8003"),
3958
- import_react3.default.createElement("option", { value: "custom" }, "\u81EA\u5B9A\u4E49\u6863\u4F4D")
3959
- ),
3960
- m.effortsMode === "custom" ? DKM_PI_LEVELS.map((lv) => import_react3.default.createElement(
3961
- "label",
3962
- { key: lv, className: "dkm-check" },
3963
- import_react3.default.createElement("input", {
3964
- type: "checkbox",
3965
- checked: !!m.effortLevels[lv],
3966
- onChange: () => {
3967
- const nl = Object.assign({}, m.effortLevels);
3968
- nl[lv] = !nl[lv];
3969
- patchModel(i, { effortLevels: nl });
3970
- }
3971
- }),
3972
- DKM_EFFORT_NAMES[lv] || lv
3973
- )) : null,
3974
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => patchModel(i, allLevels(true)) }, "\u5168\u9009"),
3975
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => patchModel(i, allLevels(false)) }, "\u53D6\u6D88\u5168\u9009")
3976
- )
3977
- )
3978
- );
4051
+ const modelRow = (m, i) => ModelRow({
4052
+ m,
4053
+ i,
4054
+ kind: cur.kind,
4055
+ onPatch: (j, patch) => patchModel(j, patch),
4056
+ onDel: (j) => patchDraft({ models: draft.models.filter((_, k) => k !== j) })
4057
+ });
4058
+ if (page === "providers") {
4059
+ return import_react3.default.createElement(ProvidersPage, {
4060
+ data,
4061
+ providers,
4062
+ loading: snap.loading,
4063
+ error: snap.error,
4064
+ openCreate: pageOpenCreate,
4065
+ onCreateHandled: () => setPageOpenCreate(false),
4066
+ // 返回主页时顺手重读目录:子页面里增删改过的 Provider 立即反映到模型编辑器
4067
+ onBack: () => {
4068
+ setPage("settings");
4069
+ refreshModels();
4070
+ }
4071
+ });
4072
+ }
3979
4073
  const body = [];
3980
4074
  if (data) {
3981
4075
  body.push(import_react3.default.createElement(
@@ -4115,6 +4209,44 @@ function ModelsView() {
4115
4209
  )
4116
4210
  ));
4117
4211
  }
4212
+ if (data) {
4213
+ const custom = providers.filter((p) => p.kind === "pi-ai");
4214
+ const missingKey = custom.filter((p) => p.apiKeyEnv && !p.apiKeyConfigured).length;
4215
+ body.push(import_react3.default.createElement(
4216
+ "div",
4217
+ { key: "pf-entry", className: "dkp-card" },
4218
+ import_react3.default.createElement(
4219
+ "div",
4220
+ { className: "dkp-rowhead" },
4221
+ import_react3.default.createElement(
4222
+ "span",
4223
+ { className: "dkp-identity" },
4224
+ import_react3.default.createElement("span", { className: "dkp-name" }, "\u6A21\u578B\u63D0\u4F9B\u5546"),
4225
+ import_react3.default.createElement("span", { className: "dkp-tag" }, custom.length + " \u4E2A\u81EA\u5B9A\u4E49\u8DEF\u7531"),
4226
+ custom.length > 0 ? import_react3.default.createElement("span", {
4227
+ className: "dkp-dot " + (missingKey > 0 ? "warn" : "ok"),
4228
+ role: "img",
4229
+ title: missingKey > 0 ? "\u6709 " + missingKey + " \u4E2A Provider \u672A\u914D\u7F6E\u5BC6\u94A5" : "\u5BC6\u94A5\u5747\u5DF2\u914D\u7F6E",
4230
+ "aria-label": missingKey > 0 ? "\u6709 Provider \u672A\u914D\u7F6E\u5BC6\u94A5" : "\u5BC6\u94A5\u5747\u5DF2\u914D\u7F6E"
4231
+ }) : null
4232
+ ),
4233
+ import_react3.default.createElement(
4234
+ "span",
4235
+ { className: "dkp-actions" },
4236
+ import_react3.default.createElement("button", {
4237
+ type: "button",
4238
+ className: "dkp-btn ghost sm",
4239
+ onClick: () => setPage("providers")
4240
+ }, "\u7BA1\u7406")
4241
+ )
4242
+ ),
4243
+ import_react3.default.createElement(
4244
+ "p",
4245
+ { className: "dkp-hint" },
4246
+ "\u65B0\u589E / \u7F16\u8F91 / \u5220\u9664\u81EA\u5B9A\u4E49\u6A21\u578B Provider\uFF08OpenAI \u517C\u5BB9\u3001Anthropic \u7B49\uFF09\uFF0C\u5199\u5165\u5B98\u65B9\u914D\u7F6E\u5E76\u70ED\u751F\u6548\uFF1BAPI Key \u5B58\u672C\u673A\u51ED\u636E\u5E93\uFF08\u6587\u4EF6\u4EC5 root \u53EF\u8BFB\uFF09\u3002"
4247
+ )
4248
+ ));
4249
+ }
4118
4250
  if (data && providers.length === 0) {
4119
4251
  body.push(import_react3.default.createElement("div", { key: "empty", className: "dkm-note" }, "\u6CA1\u6709\u53EF\u7F16\u8F91\u7684\u6A21\u578B Provider\uFF08Host \u4FA7\u5C1A\u672A\u914D\u7F6E\uFF09"));
4120
4252
  }
@@ -4136,60 +4268,586 @@ function ModelsView() {
4136
4268
  { key: "prov", className: "dkm-prov" },
4137
4269
  import_react3.default.createElement(
4138
4270
  "div",
4139
- { className: "dkm-prov-head" },
4140
- import_react3.default.createElement("span", { className: "dkm-name" }, cur.displayName),
4141
- import_react3.default.createElement("span", { className: "dkm-badge" }, cur.kind === "deepseek" ? "\u5B98\u65B9 DeepSeek" : "\u81EA\u5B9A\u4E49\u8DEF\u7531"),
4142
- import_react3.default.createElement("span", { className: "dkm-sub" }, [cur.api, cur.baseURL].filter(Boolean).join(" \xB7 "))
4143
- ),
4144
- cur.kind === "deepseek" ? import_react3.default.createElement(
4145
- "div",
4146
- { className: "dkm-controls" },
4147
- import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u6A21\u5F0F\uFF1A"),
4271
+ { className: "dkm-prov-head" },
4272
+ import_react3.default.createElement("span", { className: "dkm-name" }, cur.displayName),
4273
+ import_react3.default.createElement("span", { className: "dkm-badge" }, cur.kind === "deepseek" ? "\u5B98\u65B9 DeepSeek" : "\u81EA\u5B9A\u4E49\u8DEF\u7531"),
4274
+ import_react3.default.createElement("span", { className: "dkm-sub" }, [cur.api, cur.baseURL].filter(Boolean).join(" \xB7 "))
4275
+ ),
4276
+ cur.kind === "deepseek" ? import_react3.default.createElement(
4277
+ "div",
4278
+ { className: "dkm-controls" },
4279
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u6A21\u5F0F\uFF1A"),
4280
+ import_react3.default.createElement(
4281
+ "select",
4282
+ { className: "dkm-select", value: draft.thinking, onChange: (e) => patchDraft({ thinking: e.target.value }) },
4283
+ import_react3.default.createElement("option", { value: "enabled" }, "\u5F00\u542F"),
4284
+ import_react3.default.createElement("option", { value: "disabled" }, "\u5173\u95ED")
4285
+ ),
4286
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u9ED8\u8BA4\u5F3A\u5EA6\uFF1A"),
4287
+ import_react3.default.createElement(
4288
+ "select",
4289
+ { className: "dkm-select", value: draft.defaultEffort, onChange: (e) => patchDraft({ defaultEffort: e.target.value }) },
4290
+ DKM_DS_LEVELS.map((lv) => import_react3.default.createElement("option", { key: lv, value: lv }, DKM_EFFORT_NAMES[lv] || lv))
4291
+ )
4292
+ ) : null,
4293
+ import_react3.default.createElement(
4294
+ "div",
4295
+ { className: "dkm-toolbar" },
4296
+ import_react3.default.createElement("span", { className: "dkm-toolbar-label" }, "\u6279\u91CF\uFF08\u5168\u90E8\u6A21\u578B\uFF09\uFF1A"),
4297
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => bulkModels(() => allInputs(true)) }, "\u8F93\u5165\u5168\u9009"),
4298
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u8F93\u5165\u52FE\u9009\uFF08\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => bulkModels(() => allInputs(false)) }, "\u8F93\u5165\u53D6\u6D88\u5168\u9009"),
4299
+ cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => bulkModels(() => allLevels(true)) }, "\u5F3A\u5EA6\u5168\u9009") : null,
4300
+ cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => bulkModels(() => allLevels(false)) }, "\u5F3A\u5EA6\u53D6\u6D88\u5168\u9009") : null
4301
+ ),
4302
+ import_react3.default.createElement(
4303
+ "div",
4304
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
4305
+ draft.models.map(modelRow),
4306
+ import_react3.default.createElement("button", {
4307
+ type: "button",
4308
+ className: "dkm-add",
4309
+ onClick: () => patchDraft({
4310
+ models: draft.models.concat([{ id: "", name: "", contextWindow: "", maxTokens: "", input: ["text"], tags: [], effortsMode: "inherit", effortLevels: {} }])
4311
+ })
4312
+ }, "+ \u6DFB\u52A0\u6A21\u578B")
4313
+ ),
4314
+ import_react3.default.createElement(
4315
+ "div",
4316
+ { className: "dkm-savebar" },
4317
+ import_react3.default.createElement("span", { className: "dkm-msg" }, dirty ? "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" : "\u65E0\u672A\u4FDD\u5B58\u4FEE\u6539"),
4318
+ import_react3.default.createElement("button", { type: "button", className: "dkm-save", disabled: !dirty || saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5199\u56DE\u5B98\u65B9\u914D\u7F6E"),
4319
+ import_react3.default.createElement("button", { type: "button", className: "dkb-refresh", disabled: saving || snap.loading, onClick: refreshModels }, snap.loading ? "\u5237\u65B0\u4E2D\u2026" : "\u91CD\u65B0\u62C9\u53D6"),
4320
+ msg ? import_react3.default.createElement("span", { className: "dkm-msg " + (msg.ok ? "ok" : "err") }, msg.text) : null
4321
+ )
4322
+ ));
4323
+ }
4324
+ return import_react3.default.createElement("div", { className: "dkm-list" }, body);
4325
+ }
4326
+ function ProviderModelRow(props) {
4327
+ const m = props.m;
4328
+ const i = props.i;
4329
+ const check = (v, list, kind) => import_react3.default.createElement(
4330
+ "label",
4331
+ { key: v[0], className: "dkp-check" },
4332
+ import_react3.default.createElement("input", {
4333
+ type: "checkbox",
4334
+ checked: list.indexOf(v[0]) >= 0,
4335
+ onChange: () => {
4336
+ const next = dkmToggleIn(list, v[0]);
4337
+ props.onPatch(i, kind === "input" ? { input: next.length > 0 ? next : ["text"] } : { tags: next });
4338
+ }
4339
+ }),
4340
+ v[1]
4341
+ );
4342
+ return import_react3.default.createElement(
4343
+ "div",
4344
+ { className: "dkp-model-entry" },
4345
+ import_react3.default.createElement(
4346
+ "div",
4347
+ { className: "dkp-model-row" },
4348
+ import_react3.default.createElement("input", { className: "dkp-input", placeholder: "\u6A21\u578B id\uFF08\u5FC5\u586B\uFF09", value: m.id, onChange: (e) => props.onPatch(i, { id: e.target.value }) }),
4349
+ import_react3.default.createElement("input", { className: "dkp-input", placeholder: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09", value: m.name, onChange: (e) => props.onPatch(i, { name: e.target.value }) }),
4350
+ import_react3.default.createElement("input", { className: "dkp-input", placeholder: "\u4E0A\u4E0B\u6587\u7A97\u53E3", inputMode: "numeric", value: m.contextWindow, onChange: (e) => props.onPatch(i, { contextWindow: e.target.value }) }),
4351
+ import_react3.default.createElement("input", { className: "dkp-input", placeholder: "\u6700\u5927\u8F93\u51FA", inputMode: "numeric", value: m.maxTokens, onChange: (e) => props.onPatch(i, { maxTokens: e.target.value }) }),
4352
+ import_react3.default.createElement("button", { type: "button", className: "dkp-iconbtn danger", title: "\u5220\u9664\u8BE5\u6A21\u578B", "aria-label": "\u5220\u9664\u6A21\u578B", onClick: () => props.onDel(i) }, "\u2715")
4353
+ ),
4354
+ import_react3.default.createElement(
4355
+ "div",
4356
+ { className: "dkp-model-opts" },
4357
+ import_react3.default.createElement(
4358
+ "div",
4359
+ { className: "dkp-checks" },
4360
+ import_react3.default.createElement("span", { className: "dkp-flabel" }, "\u8F93\u5165\u7C7B\u578B\uFF1A"),
4361
+ DKM_MODALITIES.map((v) => check(v, m.input, "input")),
4362
+ import_react3.default.createElement("span", { className: "dkp-flabel dkp-dim", title: "\u6807\u6CE8\u4EC5\u5728\u672C\u9762\u677F\u5C55\u793A\u4E0E\u6301\u4E45\u5316\uFF0C\u4E0D\u53C2\u4E0E\u8BF7\u6C42\u8DEF\u7531\uFF08\u5B98\u65B9 schema \u53EA\u63A5\u53D7\u6587\u672C/\u56FE\u7247\uFF09" }, "\u6807\u6CE8\uFF1A"),
4363
+ DKM_TAGS.map((v) => check(v, m.tags, "tags")),
4364
+ import_react3.default.createElement("button", { type: "button", className: "dkp-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => props.onPatch(i, dkmAllInputs(true)) }, "\u5168\u9009"),
4365
+ import_react3.default.createElement("button", { type: "button", className: "dkp-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u52FE\u9009", onClick: () => props.onPatch(i, dkmAllInputs(false)) }, "\u53D6\u6D88\u5168\u9009")
4366
+ ),
4367
+ import_react3.default.createElement(
4368
+ "div",
4369
+ { className: "dkp-checks" },
4370
+ import_react3.default.createElement("span", { className: "dkp-flabel" }, "\u601D\u8003\u5F3A\u5EA6\uFF1A"),
4371
+ import_react3.default.createElement(
4372
+ "select",
4373
+ {
4374
+ className: "dkp-input dkp-select dkp-select-sm",
4375
+ value: m.effortsMode,
4376
+ onChange: (e) => props.onPatch(i, {
4377
+ effortsMode: e.target.value,
4378
+ effortLevels: e.target.value === "custom" && m.effortsMode !== "custom" ? { off: true, low: true, high: true } : m.effortLevels
4379
+ })
4380
+ },
4381
+ import_react3.default.createElement("option", { value: "inherit" }, "\u8DDF\u968F\u76EE\u5F55\u9ED8\u8BA4"),
4382
+ import_react3.default.createElement("option", { value: "off" }, "\u4E0D\u652F\u6301\u601D\u8003"),
4383
+ import_react3.default.createElement("option", { value: "custom" }, "\u81EA\u5B9A\u4E49\u6863\u4F4D")
4384
+ ),
4385
+ m.effortsMode === "custom" ? DKM_PI_LEVELS.map((lv) => import_react3.default.createElement(
4386
+ "label",
4387
+ { key: lv, className: "dkp-check" },
4388
+ import_react3.default.createElement("input", {
4389
+ type: "checkbox",
4390
+ checked: !!m.effortLevels[lv],
4391
+ onChange: () => {
4392
+ const nl = Object.assign({}, m.effortLevels);
4393
+ nl[lv] = !nl[lv];
4394
+ props.onPatch(i, { effortLevels: nl });
4395
+ }
4396
+ }),
4397
+ DKM_EFFORT_NAMES[lv] || lv
4398
+ )) : null,
4399
+ import_react3.default.createElement("button", { type: "button", className: "dkp-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => props.onPatch(i, dkmAllLevels(true)) }, "\u5168\u9009"),
4400
+ import_react3.default.createElement("button", { type: "button", className: "dkp-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => props.onPatch(i, dkmAllLevels(false)) }, "\u53D6\u6D88\u5168\u9009")
4401
+ )
4402
+ )
4403
+ );
4404
+ }
4405
+ function ProvidersPage(props) {
4406
+ const data = props.data;
4407
+ const providers = props.providers || [];
4408
+ const piProviders = providers.filter((p) => p.kind === "pi-ai");
4409
+ const [pfMode, setPfMode] = import_react3.default.useState(null);
4410
+ const [pfDraft, setPfDraft] = import_react3.default.useState(null);
4411
+ const [pfDirty, setPfDirty] = import_react3.default.useState(false);
4412
+ const [pfSaving, setPfSaving] = import_react3.default.useState(false);
4413
+ const [pfMsg, setPfMsg] = import_react3.default.useState(null);
4414
+ const [pfDelArm, setPfDelArm] = import_react3.default.useState(null);
4415
+ const [pfFetching, setPfFetching] = import_react3.default.useState(false);
4416
+ const [pfFetchedList, setPfFetchedList] = import_react3.default.useState(null);
4417
+ const openPfCreate = () => {
4418
+ setPfMode("create");
4419
+ setPfDraft(emptyProviderForm());
4420
+ setPfDirty(false);
4421
+ setPfMsg(null);
4422
+ setPfDelArm(null);
4423
+ setPfFetchedList(null);
4424
+ };
4425
+ const openPfEdit = (p) => {
4426
+ setPfMode("edit");
4427
+ setPfDraft(providerFormFromProvider(p));
4428
+ setPfDirty(false);
4429
+ setPfMsg(null);
4430
+ setPfDelArm(null);
4431
+ setPfFetchedList(null);
4432
+ };
4433
+ const closePf = () => {
4434
+ setPfMode(null);
4435
+ setPfDraft(null);
4436
+ setPfDirty(false);
4437
+ setPfMsg(null);
4438
+ setPfDelArm(null);
4439
+ setPfFetchedList(null);
4440
+ };
4441
+ const patchPf = (patch) => {
4442
+ setPfDraft(Object.assign({}, pfDraft, patch));
4443
+ setPfDirty(true);
4444
+ setPfMsg(null);
4445
+ };
4446
+ const patchPfModel = (i, patch) => {
4447
+ const models = pfDraft.models.slice();
4448
+ models[i] = Object.assign({}, models[i], patch);
4449
+ patchPf({ models });
4450
+ };
4451
+ const delPfModel = (i) => patchPf({ models: pfDraft.models.filter((_, j) => j !== i) });
4452
+ const addPfModel = () => patchPf({ models: pfDraft.models.concat([emptyModelDraft()]) });
4453
+ const togglePfModel = (id, name, on) => {
4454
+ const base = pfDraft.models.filter((m) => String(m.id || "").trim());
4455
+ if (on) {
4456
+ if (base.some((m) => String(m.id).trim() === id)) return;
4457
+ base.push(Object.assign(emptyModelDraft(), { id, name: name || "", input: pfDraft.defaultInput.slice() }));
4458
+ } else {
4459
+ const i = base.findIndex((m) => String(m.id).trim() === id);
4460
+ if (i >= 0) base.splice(i, 1);
4461
+ }
4462
+ patchPf({ models: base });
4463
+ };
4464
+ const setAllPfModels = (on) => {
4465
+ const base = [];
4466
+ if (on && pfFetchedList) {
4467
+ for (const mm of pfFetchedList) {
4468
+ const id = String(mm && mm.id || "").trim();
4469
+ if (!id) continue;
4470
+ base.push(Object.assign(emptyModelDraft(), { id, name: mm && mm.name || "", input: pfDraft.defaultInput.slice() }));
4471
+ }
4472
+ }
4473
+ patchPf({ models: base });
4474
+ };
4475
+ const pfSelectedCount = () => pfDraft ? pfDraft.models.filter((m) => String(m.id || "").trim()).length : 0;
4476
+ const pfModelsPayload = () => pfDraft.models.map((m) => ({
4477
+ id: String(m.id || "").trim(),
4478
+ name: String(m.name || "").trim(),
4479
+ contextWindow: m.contextWindow === "" || m.contextWindow == null ? void 0 : Number(m.contextWindow),
4480
+ maxTokens: m.maxTokens === "" || m.maxTokens == null ? void 0 : Number(m.maxTokens),
4481
+ input: m.input,
4482
+ tags: m.tags,
4483
+ effortsMode: m.effortsMode,
4484
+ effortLevels: m.effortLevels,
4485
+ raw: m.raw
4486
+ }));
4487
+ const fetchPfModels = () => {
4488
+ if (!pfDraft || pfFetching) return;
4489
+ setPfFetching(true);
4490
+ setPfMsg(null);
4491
+ fetch("dsh-dock/models", {
4492
+ method: "POST",
4493
+ headers: { "content-type": "application/json" },
4494
+ body: JSON.stringify({
4495
+ action: "listModels",
4496
+ baseURL: pfDraft.baseURL,
4497
+ api: pfDraft.api,
4498
+ apiKeyEnv: pfDraft.apiKeyEnv,
4499
+ apiKey: pfDraft.apiKey
4500
+ }),
4501
+ signal: AbortSignal.timeout(3e4)
4502
+ }).then((res) => res.json().then((b) => ({ status: res.status, body: b })).catch(() => ({ status: res.status, body: {} }))).then((r) => {
4503
+ if (r.status === 200 && r.body && r.body.ok) {
4504
+ setPfFetchedList(Array.isArray(r.body.models) ? r.body.models : []);
4505
+ } else {
4506
+ setPfMsg({ ok: false, text: r.body && r.body.error || "HTTP " + r.status });
4507
+ }
4508
+ }).catch((e) => setPfMsg({ ok: false, text: e && e.message || String(e) })).then(() => setPfFetching(false));
4509
+ };
4510
+ const savePf = () => {
4511
+ if (!pfDraft || pfSaving) return;
4512
+ const route = pfDraft.route.trim();
4513
+ const baseURL = pfDraft.baseURL.trim();
4514
+ const apiKey = pfDraft.apiKey.trim();
4515
+ let apiKeyEnv = pfDraft.apiKeyEnv.trim();
4516
+ if (apiKey && !apiKeyEnv) {
4517
+ apiKeyEnv = dkmEnvFromRoute(route);
4518
+ setPfDraft(Object.assign({}, pfDraft, { apiKeyEnv }));
4519
+ }
4520
+ const models = pfModelsPayload();
4521
+ if (!route) {
4522
+ setPfMsg({ ok: false, text: "\u8DEF\u7531 key \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u5982 modelscope\uFF09" });
4523
+ return;
4524
+ }
4525
+ if (!DKM_ROUTE_KEY_RE.test(route)) {
4526
+ setPfMsg({ ok: false, text: "\u8DEF\u7531 key \u975E\u6CD5\uFF1A\u53EA\u80FD\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u4E0E . _ -\uFF0C\u4E14\u4EE5\u5B57\u6BCD/\u6570\u5B57\u5F00\u5934" });
4527
+ return;
4528
+ }
4529
+ if (!/^https?:\/\//i.test(baseURL)) {
4530
+ setPfMsg({ ok: false, text: "baseURL \u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934" });
4531
+ return;
4532
+ }
4533
+ const ids = models.map((m) => m.id);
4534
+ if (ids.length === 0 || ids.some((x) => !x)) {
4535
+ setPfMsg({ ok: false, text: "\u6BCF\u4E2A\u6A21\u578B\u90FD\u8981\u6709 id\uFF08\u53EF\u70B9\u300C\u83B7\u53D6\u6A21\u578B\u5217\u8868\u300D\u81EA\u52A8\u62C9\u53D6\uFF09" });
4536
+ return;
4537
+ }
4538
+ if (new Set(ids).size !== ids.length) {
4539
+ setPfMsg({ ok: false, text: "\u6A21\u578B id \u6709\u91CD\u590D" });
4540
+ return;
4541
+ }
4542
+ const profile = Object.assign({}, pfDraft.extra || {});
4543
+ const dn = pfDraft.displayName.trim();
4544
+ if (dn) profile.displayName = dn;
4545
+ else delete profile.displayName;
4546
+ profile.api = pfDraft.api;
4547
+ profile.baseURL = baseURL;
4548
+ if (apiKeyEnv) profile.apiKeyEnv = apiKeyEnv;
4549
+ else delete profile.apiKeyEnv;
4550
+ profile.defaultInput = pfDraft.defaultInput;
4551
+ profile.models = models;
4552
+ const payload = {
4553
+ action: pfMode === "create" ? "create" : "update",
4554
+ route,
4555
+ profile,
4556
+ revisions: data && data.revisions || {}
4557
+ };
4558
+ if (apiKey) payload.apiKey = apiKey;
4559
+ setPfSaving(true);
4560
+ setPfMsg(null);
4561
+ fetch("dsh-dock/models", {
4562
+ method: "POST",
4563
+ headers: { "content-type": "application/json" },
4564
+ body: JSON.stringify(payload),
4565
+ signal: AbortSignal.timeout(3e4)
4566
+ }).then((res) => res.json().then((b) => ({ status: res.status, body: b })).catch(() => ({ status: res.status, body: {} }))).then((r) => {
4567
+ if (r.status === 200 && r.body && r.body.ok) {
4568
+ setPfMsg({ ok: true, text: "Provider\u300C" + route + "\u300D\u5DF2" + (pfMode === "create" ? "\u65B0\u589E" : "\u66F4\u65B0") + "\uFF1B\u4F1A\u8BDD\u6A21\u578B\u9009\u62E9\u5668\u5373\u65F6\u53EF\u9009" });
4569
+ setPfMode(null);
4570
+ setPfDraft(null);
4571
+ setPfDirty(false);
4572
+ modelsStore.load();
4573
+ } else {
4574
+ setPfMsg({ ok: false, text: r.body && r.body.error || "HTTP " + r.status });
4575
+ }
4576
+ }).catch((e) => setPfMsg({ ok: false, text: e && e.message || String(e) })).then(() => setPfSaving(false));
4577
+ };
4578
+ const deletePf = (p) => {
4579
+ const route = p.route || p.id;
4580
+ if (pfDelArm !== route) {
4581
+ setPfDelArm(route);
4582
+ setPfMsg(null);
4583
+ return;
4584
+ }
4585
+ setPfDelArm(null);
4586
+ setPfSaving(true);
4587
+ setPfMsg(null);
4588
+ fetch("dsh-dock/models", {
4589
+ method: "POST",
4590
+ headers: { "content-type": "application/json" },
4591
+ body: JSON.stringify({ action: "delete", route, revisions: data && data.revisions || {} }),
4592
+ signal: AbortSignal.timeout(2e4)
4593
+ }).then((res) => res.json().then((b) => ({ status: res.status, body: b })).catch(() => ({ status: res.status, body: {} }))).then((r) => {
4594
+ if (r.status === 200 && r.body && r.body.ok) {
4595
+ setPfMsg({ ok: true, text: "Provider\u300C" + route + "\u300D\u5DF2\u5220\u9664" });
4596
+ if (pfMode === "edit" && pfDraft && pfDraft.route === route) {
4597
+ setPfMode(null);
4598
+ setPfDraft(null);
4599
+ }
4600
+ modelsStore.load();
4601
+ } else {
4602
+ setPfMsg({ ok: false, text: r.body && r.body.error || "HTTP " + r.status });
4603
+ }
4604
+ }).catch((e) => setPfMsg({ ok: false, text: e && e.message || String(e) })).then(() => setPfSaving(false));
4605
+ };
4606
+ const openedRef = import_react3.default.useRef(false);
4607
+ import_react3.default.useEffect(() => {
4608
+ if (props.openCreate && !openedRef.current) {
4609
+ openedRef.current = true;
4610
+ openPfCreate();
4611
+ if (typeof props.onCreateHandled === "function") props.onCreateHandled();
4612
+ }
4613
+ }, []);
4614
+ const input = (p) => import_react3.default.createElement("input", Object.assign({ className: "dkp-input" }, p));
4615
+ const field = (label, node, hint) => import_react3.default.createElement(
4616
+ "div",
4617
+ { className: "dkp-field", key: label },
4618
+ import_react3.default.createElement("label", { className: "dkp-flabel" }, label),
4619
+ node,
4620
+ hint ? import_react3.default.createElement("p", { className: "dkp-hint" }, hint) : null
4621
+ );
4622
+ const editor = !pfDraft ? null : import_react3.default.createElement(
4623
+ "div",
4624
+ { className: "dkp-editor" },
4625
+ import_react3.default.createElement(
4626
+ "div",
4627
+ { className: "dkp-editor-head" },
4628
+ import_react3.default.createElement("span", { className: "dkp-editor-title" }, pfMode === "create" ? "\u65B0\u589E Provider" : "\u7F16\u8F91 Provider"),
4629
+ pfMode === "edit" ? import_react3.default.createElement("span", { className: "dkp-editor-route" }, pfDraft.route) : null
4630
+ ),
4631
+ field("\u8DEF\u7531 key", input({
4632
+ value: pfDraft.route,
4633
+ disabled: pfMode === "edit",
4634
+ placeholder: "acme-gateway",
4635
+ onChange: (e) => patchPf({ route: e.target.value })
4636
+ }), pfMode === "edit" ? "\u8DEF\u7531 key \u662F\u914D\u7F6E\u952E\uFF0C\u4FDD\u5B58\u540E\u4E0D\u5EFA\u8BAE\u4FEE\u6539\u3002" : "\u552F\u4E00\u6807\u8BC6\uFF1A\u53EA\u80FD\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u4E0E . _ -\uFF0C\u4E14\u4EE5\u5B57\u6BCD/\u6570\u5B57\u5F00\u5934\u3002"),
4637
+ field("\u663E\u793A\u540D\u79F0", input({
4638
+ value: pfDraft.displayName,
4639
+ placeholder: pfDraft.route || "\u5982 \u9B54\u642D ModelScope",
4640
+ onChange: (e) => patchPf({ displayName: e.target.value })
4641
+ })),
4642
+ field("baseURL", input({
4643
+ value: pfDraft.baseURL,
4644
+ placeholder: pfDraft.api === "anthropic-messages" ? "https://gateway.example" : "https://gateway.example/v1",
4645
+ onChange: (e) => patchPf({ baseURL: e.target.value })
4646
+ }), "OpenAI \u517C\u5BB9\u7AEF\u70B9\u901A\u5E38\u4EE5 /v1 \u7ED3\u5C3E\uFF1BAnthropic Messages \u7531 SDK \u81EA\u884C\u8FFD\u52A0 /v1/messages\u3002"),
4647
+ field("API \u534F\u8BAE", import_react3.default.createElement(
4648
+ "select",
4649
+ {
4650
+ className: "dkp-input dkp-select",
4651
+ value: pfDraft.api,
4652
+ onChange: (e) => patchPf({ api: e.target.value })
4653
+ },
4654
+ DKM_APIS.map(([v, label]) => import_react3.default.createElement("option", { key: v, value: v }, label))
4655
+ )),
4656
+ field("API Key", import_react3.default.createElement("input", {
4657
+ className: "dkp-input",
4658
+ type: "password",
4659
+ autoComplete: "new-password",
4660
+ placeholder: pfMode === "edit" ? "\u7559\u7A7A = \u4E0D\u6539\u52A8\u5DF2\u5B58\u5BC6\u94A5" : "sk-\u2026\uFF08\u4EC5\u672C\u6B21\u5199\u5165\u672C\u673A\u51ED\u636E\u5E93\uFF0C\u4E0D\u56DE\u663E\uFF09",
4661
+ value: pfDraft.apiKey,
4662
+ onChange: (e) => patchPf({ apiKey: e.target.value })
4663
+ })),
4664
+ import_react3.default.createElement(
4665
+ "details",
4666
+ { className: "dkp-customized" },
4667
+ import_react3.default.createElement("summary", { className: "dkp-summary" }, "\u81EA\u5B9A\u4E49\u8BBE\u7F6E"),
4668
+ import_react3.default.createElement(
4669
+ "div",
4670
+ { className: "dkp-custombody" },
4671
+ field("apiKeyEnv", input({
4672
+ value: pfDraft.apiKeyEnv,
4673
+ placeholder: "\u7559\u7A7A = \u6309\u8DEF\u7531 key \u81EA\u52A8\u751F\u6210\uFF08" + (dkmEnvFromRoute(pfDraft.route) || "ROUTE_API_KEY") + "\uFF09",
4674
+ onChange: (e) => patchPf({ apiKeyEnv: e.target.value })
4675
+ }), "\u51ED\u636E\u5E93\u6761\u76EE\u540D\uFF1B\u7559\u7A7A\u65F6\u6309\u8DEF\u7531 key \u63A8\u5BFC\uFF0C\u4E00\u822C\u65E0\u9700\u586B\u5199\u3002"),
4676
+ import_react3.default.createElement(
4677
+ "div",
4678
+ { className: "dkp-field" },
4679
+ import_react3.default.createElement("span", { className: "dkp-flabel" }, "\u9ED8\u8BA4\u8F93\u5165\u7C7B\u578B"),
4680
+ import_react3.default.createElement(
4681
+ "div",
4682
+ { className: "dkp-checks" },
4683
+ DKM_MODALITIES.map(([v, label]) => import_react3.default.createElement(
4684
+ "label",
4685
+ { key: v, className: "dkp-check" },
4686
+ import_react3.default.createElement("input", {
4687
+ type: "checkbox",
4688
+ checked: pfDraft.defaultInput.indexOf(v) >= 0,
4689
+ onChange: () => {
4690
+ const next = dkmToggleIn(pfDraft.defaultInput, v);
4691
+ patchPf({ defaultInput: next.length > 0 ? next : ["text"] });
4692
+ }
4693
+ }),
4694
+ label
4695
+ )),
4696
+ import_react3.default.createElement("span", { className: "dkp-hint" }, "\u6A21\u578B\u672A\u5355\u72EC\u58F0\u660E\u8F93\u5165\u7C7B\u578B\u65F6\u7EE7\u627F\u8FD9\u91CC\u3002")
4697
+ )
4698
+ )
4699
+ )
4700
+ ),
4701
+ import_react3.default.createElement(
4702
+ "section",
4703
+ { className: "dkp-catalog", "aria-label": "\u6A21\u578B" },
4704
+ import_react3.default.createElement(
4705
+ "div",
4706
+ { className: "dkp-catalog-head" },
4148
4707
  import_react3.default.createElement(
4149
- "select",
4150
- { className: "dkm-select", value: draft.thinking, onChange: (e) => patchDraft({ thinking: e.target.value }) },
4151
- import_react3.default.createElement("option", { value: "enabled" }, "\u5F00\u542F"),
4152
- import_react3.default.createElement("option", { value: "disabled" }, "\u5173\u95ED")
4708
+ "div",
4709
+ { className: "dkp-catalog-titlewrap" },
4710
+ import_react3.default.createElement("span", { className: "dkp-catalog-title" }, "\u6A21\u578B"),
4711
+ import_react3.default.createElement(
4712
+ "span",
4713
+ { className: "dkp-catalog-meta" },
4714
+ "\u5DF2\u9009 " + pfSelectedCount() + " \u4E2A" + (pfFetchedList ? " / \u4E0A\u6E38\u5171 " + pfFetchedList.length + " \u4E2A" : "")
4715
+ )
4153
4716
  ),
4154
- import_react3.default.createElement("span", { className: "dkm-label" }, "\u9ED8\u8BA4\u5F3A\u5EA6\uFF1A"),
4155
4717
  import_react3.default.createElement(
4156
- "select",
4157
- { className: "dkm-select", value: draft.defaultEffort, onChange: (e) => patchDraft({ defaultEffort: e.target.value }) },
4158
- DKM_DS_LEVELS.map((lv) => import_react3.default.createElement("option", { key: lv, value: lv }, DKM_EFFORT_NAMES[lv] || lv))
4718
+ "div",
4719
+ { className: "dkp-actions" },
4720
+ import_react3.default.createElement("button", {
4721
+ type: "button",
4722
+ className: "dkp-btn ghost sm",
4723
+ disabled: pfFetching || !pfDraft.baseURL,
4724
+ title: "\u8C03\u7528 baseURL/models \u62C9\u53D6\u8BE5\u7AEF\u70B9\u53EF\u7528\u6A21\u578B\uFF0C\u52FE\u9009\u540E\u52A0\u5165\u4E0B\u65B9\u5217\u8868",
4725
+ onClick: fetchPfModels
4726
+ }, pfFetching ? "\u62C9\u53D6\u4E2D\u2026" : "\u83B7\u53D6\u6A21\u578B\u5217\u8868"),
4727
+ pfFetchedList && pfFetchedList.length > 0 ? import_react3.default.createElement("button", { type: "button", className: "dkp-btn ghost sm", title: "\u52FE\u9009\u4E0A\u6E38\u8FD4\u56DE\u7684\u5168\u90E8\u6A21\u578B", onClick: () => setAllPfModels(true) }, "\u5168\u9009") : null,
4728
+ pfFetchedList && pfFetchedList.length > 0 ? import_react3.default.createElement("button", { type: "button", className: "dkp-btn ghost sm", title: "\u6E05\u7A7A\u5DF2\u52FE\u9009\u7684\u6A21\u578B", onClick: () => setAllPfModels(false) }, "\u6E05\u7A7A") : null
4159
4729
  )
4160
- ) : null,
4161
- import_react3.default.createElement(
4162
- "div",
4163
- { className: "dkm-toolbar" },
4164
- import_react3.default.createElement("span", { className: "dkm-toolbar-label" }, "\u6279\u91CF\uFF08\u5168\u90E8\u6A21\u578B\uFF09\uFF1A"),
4165
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => bulkModels(() => allInputs(true)) }, "\u8F93\u5165\u5168\u9009"),
4166
- import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u8F93\u5165\u52FE\u9009\uFF08\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => bulkModels(() => allInputs(false)) }, "\u8F93\u5165\u53D6\u6D88\u5168\u9009"),
4167
- cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => bulkModels(() => allLevels(true)) }, "\u5F3A\u5EA6\u5168\u9009") : null,
4168
- cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => bulkModels(() => allLevels(false)) }, "\u5F3A\u5EA6\u53D6\u6D88\u5168\u9009") : null
4169
4730
  ),
4170
- import_react3.default.createElement(
4731
+ pfFetchedList ? import_react3.default.createElement(
4171
4732
  "div",
4172
- { style: { display: "flex", flexDirection: "column", gap: "8px" } },
4173
- draft.models.map(modelRow),
4174
- import_react3.default.createElement("button", {
4175
- type: "button",
4176
- className: "dkm-add",
4177
- onClick: () => patchDraft({
4178
- models: draft.models.concat([{ id: "", name: "", contextWindow: "", maxTokens: "", input: ["text"], tags: [], effortsMode: "inherit", effortLevels: {} }])
4179
- })
4180
- }, "+ \u6DFB\u52A0\u6A21\u578B")
4733
+ { className: "dkp-picker" },
4734
+ pfFetchedList.length === 0 ? import_react3.default.createElement("div", { className: "dkp-hint" }, "\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE\u4EFB\u4F55\u6A21\u578B") : pfFetchedList.map((mm) => {
4735
+ const id = String(mm && mm.id || "").trim();
4736
+ if (!id) return null;
4737
+ const checked = pfDraft.models.some((m) => String(m.id || "").trim() === id);
4738
+ return import_react3.default.createElement(
4739
+ "label",
4740
+ { key: id },
4741
+ import_react3.default.createElement("input", {
4742
+ type: "checkbox",
4743
+ checked,
4744
+ onChange: () => togglePfModel(id, mm && mm.name || "", !checked)
4745
+ }),
4746
+ import_react3.default.createElement("span", { className: "dkp-picker-id" }, id),
4747
+ mm && mm.name ? import_react3.default.createElement("span", { className: "dkp-picker-name" }, mm.name) : null
4748
+ );
4749
+ })
4750
+ ) : null,
4751
+ pfDraft.models.length === 0 ? import_react3.default.createElement("div", { className: "dkp-empty" }, "\u8FD8\u6CA1\u6709\u6A21\u578B\u2014\u2014\u70B9\u300C\u83B7\u53D6\u6A21\u578B\u5217\u8868\u300D\u52FE\u9009\u4E0A\u6E38\u6A21\u578B\uFF0C\u6216\u300C\u6DFB\u52A0\u6A21\u578B\u300D\u624B\u52A8\u586B\u5199\u3002") : import_react3.default.createElement(
4752
+ "div",
4753
+ { className: "dkp-model-list" },
4754
+ pfDraft.models.map((m, i) => import_react3.default.createElement(ProviderModelRow, { key: i, m, i, onPatch: patchPfModel, onDel: delPfModel }))
4181
4755
  ),
4756
+ import_react3.default.createElement("button", { type: "button", className: "dkp-addmodel", onClick: addPfModel }, "\uFF0B \u6DFB\u52A0\u6A21\u578B")
4757
+ ),
4758
+ pfMsg && !pfMsg.ok ? import_react3.default.createElement("p", { className: "dkp-error" }, pfMsg.text) : null,
4759
+ import_react3.default.createElement(
4760
+ "div",
4761
+ { className: "dkp-editor-actions" },
4762
+ import_react3.default.createElement("span", { className: "dkp-hint" }, pfDirty ? "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" : ""),
4763
+ import_react3.default.createElement("button", { type: "button", className: "dkp-btn ghost", onClick: closePf }, "\u53D6\u6D88"),
4182
4764
  import_react3.default.createElement(
4183
- "div",
4184
- { className: "dkm-savebar" },
4185
- import_react3.default.createElement("span", { className: "dkm-msg" }, dirty ? "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" : "\u65E0\u672A\u4FDD\u5B58\u4FEE\u6539"),
4186
- import_react3.default.createElement("button", { type: "button", className: "dkm-save", disabled: !dirty || saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5199\u56DE\u5B98\u65B9\u914D\u7F6E"),
4187
- import_react3.default.createElement("button", { type: "button", className: "dkb-refresh", disabled: saving || snap.loading, onClick: refreshModels }, snap.loading ? "\u5237\u65B0\u4E2D\u2026" : "\u91CD\u65B0\u62C9\u53D6"),
4188
- msg ? import_react3.default.createElement("span", { className: "dkm-msg " + (msg.ok ? "ok" : "err") }, msg.text) : null
4765
+ "button",
4766
+ { type: "button", className: "dkp-btn primary", disabled: pfSaving, onClick: savePf },
4767
+ pfSaving ? "\u4FDD\u5B58\u4E2D\u2026" : pfMode === "create" ? "\u65B0\u589E Provider" : "\u4FDD\u5B58\u4FEE\u6539"
4189
4768
  )
4769
+ )
4770
+ );
4771
+ const body = [
4772
+ import_react3.default.createElement("button", { key: "back", type: "button", className: "dkp-back", onClick: props.onBack }, "\u2190 \u6A21\u578B\u8BBE\u7F6E"),
4773
+ import_react3.default.createElement("h2", { key: "title", className: "dkp-title" }, "\u6A21\u578B\u63D0\u4F9B\u5546"),
4774
+ import_react3.default.createElement(
4775
+ "p",
4776
+ { key: "intro", className: "dkp-intro" },
4777
+ "\u65B0\u589E / \u7F16\u8F91 / \u5220\u9664\u81EA\u5B9A\u4E49\u6A21\u578B Provider\uFF08OpenAI \u517C\u5BB9\u3001Anthropic \u7B49\uFF09\uFF0C\u5199\u5165\u5B98\u65B9\u914D\u7F6E\u5E76\u70ED\u751F\u6548\uFF1BAPI Key \u5B58\u672C\u673A\u51ED\u636E\u5E93\uFF08\u6587\u4EF6\u4EC5 root \u53EF\u8BFB\uFF09\u3002\u5185\u7F6E DeepSeek \u5B98\u65B9 Provider \u5728\u672C\u9875\u53EA\u8BFB\uFF0C\u5176\u6A21\u578B\u76EE\u5F55\u8BF7\u56DE\u300C\u6A21\u578B\u8BBE\u7F6E\u300D\u7F16\u8F91\u3002"
4778
+ )
4779
+ ];
4780
+ if (props.error) {
4781
+ body.push(import_react3.default.createElement("p", { key: "err", className: "dkp-error" }, "\u6A21\u578B\u76EE\u5F55\u62C9\u53D6\u5931\u8D25\uFF1A" + props.error));
4782
+ }
4783
+ if (pfMsg && !pfDraft) {
4784
+ body.push(import_react3.default.createElement("p", { key: "msg", className: "dkp-msg " + (pfMsg.ok ? "ok" : "err"), role: "status" }, pfMsg.text));
4785
+ }
4786
+ if (!data) {
4787
+ body.push(import_react3.default.createElement("p", { key: "loading", className: "dkp-hint" }, props.loading ? "\u6B63\u5728\u62C9\u53D6\u6A21\u578B\u76EE\u5F55\u2026" : "\u7B49\u5F85\u62C9\u53D6\u6A21\u578B\u76EE\u5F55"));
4788
+ } else if (piProviders.length === 0) {
4789
+ body.push(import_react3.default.createElement("div", { key: "empty", className: "dkp-empty" }, "\u8FD8\u6CA1\u6709\u81EA\u5B9A\u4E49 Provider\u2014\u2014\u70B9\u4E0B\u65B9\u300C\u65B0\u589E Provider\u300D\u6DFB\u52A0\u3002"));
4790
+ } else {
4791
+ body.push(import_react3.default.createElement(
4792
+ "ul",
4793
+ { key: "rows", className: "dkp-rows" },
4794
+ piProviders.map((p) => {
4795
+ const route = p.route || p.id;
4796
+ const editing = pfMode === "edit" && !!pfDraft && pfDraft.route === route;
4797
+ const keyMissing = !!(p.apiKeyEnv && !p.apiKeyConfigured);
4798
+ return import_react3.default.createElement(
4799
+ "li",
4800
+ { key: p.id, className: "dkp-card" },
4801
+ import_react3.default.createElement(
4802
+ "div",
4803
+ { className: "dkp-rowhead" },
4804
+ import_react3.default.createElement(
4805
+ "span",
4806
+ { className: "dkp-identity" },
4807
+ import_react3.default.createElement("span", { className: "dkp-name" }, p.displayName || route),
4808
+ import_react3.default.createElement("span", { className: "dkp-tag" }, route),
4809
+ import_react3.default.createElement("span", { className: "dkp-tag" }, (p.models ? p.models.length : 0) + " \u6A21\u578B"),
4810
+ import_react3.default.createElement("span", {
4811
+ className: "dkp-dot " + (keyMissing ? "missing" : "ok"),
4812
+ role: "img",
4813
+ title: p.apiKeyEnv ? p.apiKeyConfigured ? p.apiKeyEnv + " \u5DF2\u914D\u7F6E" : p.apiKeyEnv + " \u672A\u914D\u7F6E" : "\u672A\u8BBE apiKeyEnv",
4814
+ "aria-label": p.apiKeyEnv ? p.apiKeyConfigured ? "\u5BC6\u94A5\u5DF2\u914D\u7F6E" : "\u5BC6\u94A5\u672A\u914D\u7F6E" : "\u672A\u8BBE apiKeyEnv"
4815
+ }),
4816
+ p.apiKeyEnv ? import_react3.default.createElement(
4817
+ "span",
4818
+ { className: "dkp-tag" + (keyMissing ? " warn" : " ok") },
4819
+ p.apiKeyConfigured ? p.apiKeyEnv + " \u5DF2\u914D\u7F6E" : p.apiKeyEnv + " \u672A\u914D\u7F6E"
4820
+ ) : null
4821
+ ),
4822
+ import_react3.default.createElement(
4823
+ "span",
4824
+ { className: "dkp-actions" },
4825
+ import_react3.default.createElement("button", {
4826
+ type: "button",
4827
+ className: "dkp-btn ghost sm",
4828
+ "aria-expanded": editing,
4829
+ onClick: () => editing ? closePf() : openPfEdit(p)
4830
+ }, editing ? "\u6536\u8D77" : "\u7F16\u8F91"),
4831
+ import_react3.default.createElement("button", {
4832
+ type: "button",
4833
+ className: "dkp-btn danger sm" + (pfDelArm === route ? " arm" : ""),
4834
+ title: pfDelArm === route ? "\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u5220\u9664" : "\u5220\u9664\u8BE5 Provider",
4835
+ onClick: () => deletePf(p)
4836
+ }, pfDelArm === route ? "\u786E\u8BA4\u5220\u9664\uFF1F" : "\u5220\u9664")
4837
+ )
4838
+ ),
4839
+ import_react3.default.createElement("div", { className: "dkp-sub" }, [p.api, p.baseURL].filter(Boolean).join(" \xB7 ")),
4840
+ editing ? editor : null
4841
+ );
4842
+ })
4190
4843
  ));
4191
4844
  }
4192
- return import_react3.default.createElement("div", { className: "dkm-list" }, body);
4845
+ body.push(import_react3.default.createElement(
4846
+ "div",
4847
+ { key: "add", className: "dkp-addblock" },
4848
+ pfMode === "create" && pfDraft ? import_react3.default.createElement("div", { className: "dkp-card dkp-addcard" }, editor) : import_react3.default.createElement("button", { type: "button", className: "dkp-addbtn", onClick: openPfCreate }, "\uFF0B \u65B0\u589E Provider")
4849
+ ));
4850
+ return import_react3.default.createElement("div", { className: "dkp-page" }, body);
4193
4851
  }
4194
4852
  function ModelsStat() {
4195
4853
  const snap = useModels();
@@ -4205,7 +4863,7 @@ var feature2 = {
4205
4863
  name: "\u6A21\u578B\u8BBE\u7F6E",
4206
4864
  order: 100,
4207
4865
  accent: "#22d3ee",
4208
- description: "\u7F16\u8F91\u5404 Provider \u6A21\u578B\u76EE\u5F55\uFF1A\u8F93\u5165\u7C7B\u578B\uFF08\u6587\u672C/\u56FE\u7247 + \u6807\u6CE8\uFF09\u4E0E\u601D\u8003\u5F3A\u5EA6\u6863\u4F4D\uFF1B\u5199\u56DE\u5B98\u65B9\u914D\u7F6E\u70ED\u751F\u6548\uFF0C\u4F1A\u8BDD\u6A21\u578B\u9009\u62E9\u5668\u5373\u65F6\u53EF\u9009",
4866
+ description: "\u7F16\u8F91\u5404 Provider \u6A21\u578B\u76EE\u5F55\uFF08\u8F93\u5165\u7C7B\u578B/\u601D\u8003\u5F3A\u5EA6\u6863\u4F4D\uFF09\uFF1B\u81EA\u5B9A\u4E49 Provider \u7684\u589E\u5220\u6539\u5728\u300C\u6A21\u578B\u63D0\u4F9B\u5546\u300D\u5B50\u9875\u9762\uFF0C\u5199\u56DE\u5B98\u65B9\u914D\u7F6E\u70ED\u751F\u6548\uFF0C\u4F1A\u8BDD\u6A21\u578B\u9009\u62E9\u5668\u5373\u65F6\u53EF\u9009",
4209
4867
  css: [
4210
4868
  ".dkm-note{color:var(--dsw-alias-label-secondary);font-size:12px;}",
4211
4869
  ".dkm-warn{color:var(--dk-warn);}",
@@ -4250,7 +4908,99 @@ var feature2 = {
4250
4908
  ".dkm-mini{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-family:inherit;font-size:11px;flex:none;}",
4251
4909
  ".dkm-mini:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary);}",
4252
4910
  ".dkm-toolbar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;border-top:1px dashed var(--dsw-alias-border-l1);padding-top:8px;}",
4253
- ".dkm-toolbar-label{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none;}"
4911
+ ".dkm-toolbar-label{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none;}",
4912
+ // ---- 模型提供商子页面(dkp- 前缀):对齐官方「模型」设置页的排版与尺寸 ----
4913
+ // 官方用量参考:section 限宽 720、行卡片 .5px 描边 + radius-xl + 12/14 内距、
4914
+ // 输入高 32、按钮高 36(行内小按钮 28)、新增按钮 44 虚线、编辑器 bg-module-platform。
4915
+ ".dkp-page{width:100%;max-width:720px;display:flex;flex-direction:column;gap:12px;color:var(--dsw-alias-label-primary);}",
4916
+ ".dkp-back{box-sizing:border-box;align-self:flex-start;cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:var(--dsw-radius-sm,6px);padding:4px 8px;margin-left:-8px;font-family:inherit;font-size:12px;line-height:18px;}",
4917
+ ".dkp-back:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
4918
+ ".dkp-title{margin:0;font-size:16px;font-weight:500;line-height:24px;}",
4919
+ ".dkp-intro{margin:0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px;}",
4920
+ ".dkp-rows{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px;}",
4921
+ ".dkp-card{box-sizing:border-box;margin:0;border:.5px solid var(--dsw-alias-settings-card-stroke,var(--dsw-alias-border-l2));background:var(--dsw-alias-settings-card-fill,var(--dsw-alias-bg-layer-1));border-radius:var(--dsw-radius-xl,12px);padding:12px 14px;display:flex;flex-direction:column;gap:10px;}",
4922
+ ".dkp-rowhead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}",
4923
+ ".dkp-identity{display:inline-flex;align-items:center;gap:6px;min-width:0;flex-wrap:wrap;}",
4924
+ ".dkp-name{font-size:14px;font-weight:500;line-height:22px;}",
4925
+ ".dkp-tag{flex:none;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs,4px);color:var(--dsw-alias-label-secondary);padding:1px 6px;font-size:11px;line-height:16px;}",
4926
+ ".dkp-tag.ok{color:var(--dsw-alias-state-success-primary);border-color:currentColor;}",
4927
+ ".dkp-tag.warn{color:var(--dk-warn);border-color:currentColor;}",
4928
+ ".dkp-dot{box-sizing:border-box;flex:none;width:8px;height:8px;border-radius:50%;display:inline-block;}",
4929
+ ".dkp-dot.ok{background:var(--dsw-alias-state-success-primary);}",
4930
+ ".dkp-dot.missing{background:var(--dsw-alias-state-error-primary);}",
4931
+ ".dkp-dot.warn{background:var(--dk-warn);}",
4932
+ ".dkp-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
4933
+ ".dkp-hint{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;}",
4934
+ ".dkp-dim{color:var(--dsw-alias-label-tertiary);}",
4935
+ ".dkp-error{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;}",
4936
+ ".dkp-msg{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;}",
4937
+ ".dkp-msg.ok{color:var(--dsw-alias-state-success-primary);}",
4938
+ ".dkp-msg.err{color:var(--dsw-alias-state-error-primary);}",
4939
+ ".dkp-actions{flex:none;margin-left:auto;display:inline-flex;align-items:center;gap:4px;}",
4940
+ ".dkp-btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:36px;padding:0 14px;border:none;border-radius:var(--dsw-radius-md,8px);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;}",
4941
+ ".dkp-btn.ghost{border:.5px solid var(--dsw-alias-border-l3);}",
4942
+ ".dkp-btn.ghost:hover{background:var(--dsw-alias-interactive-bg-hover);}",
4943
+ ".dkp-btn.primary{background:var(--dsw-alias-button-primary-fill,var(--dk-accent));color:var(--dsw-alias-label-primary-foreground,#fff);}",
4944
+ ".dkp-btn.primary:hover{background:var(--dsw-alias-button-primary-hover,var(--dk-accent));}",
4945
+ ".dkp-btn.danger{color:var(--dsw-alias-state-error-primary);}",
4946
+ ".dkp-btn.danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgba(220,38,38,.1));}",
4947
+ ".dkp-btn.arm{color:var(--dsw-alias-state-error-primary);border-color:currentColor;}",
4948
+ ".dkp-btn.sm{height:28px;padding:0 10px;border-radius:var(--dsw-radius-sm,6px);font-size:12px;line-height:18px;}",
4949
+ ".dkp-btn[disabled]{opacity:.4;cursor:default;}",
4950
+ ".dkp-editor{border-radius:var(--dsw-radius-lg,10px);background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-1));padding:14px 16px;display:flex;flex-direction:column;gap:14px;}",
4951
+ ".dkp-editor-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;}",
4952
+ ".dkp-editor-title{font-size:14px;font-weight:500;line-height:22px;}",
4953
+ ".dkp-editor-route{color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,monospace);font-size:12px;line-height:18px;}",
4954
+ ".dkp-field{display:flex;flex-direction:column;gap:6px;min-width:0;}",
4955
+ ".dkp-flabel{display:inline-flex;align-items:center;gap:10px;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;}",
4956
+ ".dkp-input{box-sizing:border-box;width:100%;height:32px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4,var(--dsw-alias-border-l2));border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;}",
4957
+ ".dkp-input:focus{border-color:var(--dsw-alias-state-business-primary,var(--dk-accent));outline:none;}",
4958
+ ".dkp-input::placeholder{color:var(--dsw-alias-label-dimmed,var(--dsw-alias-label-tertiary));}",
4959
+ ".dkp-input:disabled{opacity:.6;cursor:default;}",
4960
+ "select.dkp-input{appearance:auto;cursor:pointer;max-width:240px;}",
4961
+ ".dkp-select-sm{height:28px;max-width:160px;font-size:12px;line-height:18px;}",
4962
+ ".dkp-checks{display:flex;align-items:center;gap:16px;flex-wrap:wrap;min-height:32px;}",
4963
+ ".dkp-check{display:inline-flex;align-items:center;gap:6px;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;}",
4964
+ ".dkp-check input{accent-color:var(--dk-accent);margin:0;}",
4965
+ ".dkp-mini{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-tertiary);padding:1px 8px;font-family:inherit;font-size:11px;line-height:16px;flex:none;}",
4966
+ ".dkp-mini:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary);}",
4967
+ ".dkp-customized{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:10px;}",
4968
+ ".dkp-summary{display:flex;align-items:center;gap:6px;width:fit-content;margin-left:-4px;padding:2px 4px;border-radius:var(--dsw-radius-sm,6px);list-style:none;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;}",
4969
+ ".dkp-summary::-webkit-details-marker{display:none;}",
4970
+ '.dkp-summary::before{content:"";width:5px;height:5px;border-right:1.5px solid;border-bottom:1.5px solid;transform:rotate(-45deg);transition:transform .12s;}',
4971
+ ".dkp-customized[open]>.dkp-summary::before{transform:rotate(45deg);}",
4972
+ ".dkp-summary:hover{color:var(--dsw-alias-label-primary);}",
4973
+ ".dkp-custombody{display:flex;flex-direction:column;gap:12px;padding-top:12px;}",
4974
+ ".dkp-catalog{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px;display:flex;flex-direction:column;gap:10px;}",
4975
+ ".dkp-catalog-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}",
4976
+ ".dkp-catalog-titlewrap{display:flex;flex-direction:column;gap:2px;}",
4977
+ ".dkp-catalog-title{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;}",
4978
+ ".dkp-catalog-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;}",
4979
+ ".dkp-model-list{display:flex;flex-direction:column;gap:8px;}",
4980
+ ".dkp-model-entry{border:.5px solid var(--dsw-alias-border-l4,var(--dsw-alias-border-l2));border-radius:var(--dsw-radius-lg,10px);padding:6px;display:flex;flex-direction:column;gap:6px;}",
4981
+ ".dkp-model-row{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,.7fr) minmax(0,.7fr) auto;gap:6px;align-items:center;}",
4982
+ ".dkp-model-row .dkp-input{height:30px;padding:0 8px;font-size:13px;}",
4983
+ ".dkp-model-opts{display:flex;flex-direction:column;gap:4px;padding:2px 2px 0;}",
4984
+ ".dkp-iconbtn{box-sizing:border-box;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:var(--dsw-radius-sm,6px);background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:12px;line-height:1;}",
4985
+ ".dkp-iconbtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
4986
+ ".dkp-iconbtn.danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgba(220,38,38,.1));color:var(--dsw-alias-state-error-primary);}",
4987
+ ".dkp-addmodel{box-sizing:border-box;align-self:flex-start;display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-sm,6px);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer;}",
4988
+ ".dkp-addmodel:hover{background:var(--dsw-alias-interactive-bg-hover);}",
4989
+ ".dkp-picker{max-height:320px;overflow-y:auto;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md,8px);padding:4px 6px;display:flex;flex-direction:column;gap:2px;}",
4990
+ ".dkp-picker label{display:flex;align-items:center;gap:8px;min-width:0;cursor:pointer;padding:6px 8px;border-radius:var(--dsw-radius-md,8px);}",
4991
+ ".dkp-picker label:hover{background:var(--dsw-alias-interactive-bg-hover);}",
4992
+ ".dkp-picker input{accent-color:var(--dk-accent);margin:0;flex:none;}",
4993
+ ".dkp-picker-id{flex:none;font-family:var(--ds-font-family-code,ui-monospace,monospace);font-size:13px;line-height:20px;}",
4994
+ ".dkp-picker-name{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
4995
+ ".dkp-empty{border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg,10px);padding:12px;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;}",
4996
+ ".dkp-addblock{display:flex;align-items:stretch;}",
4997
+ ".dkp-addbtn{box-sizing:border-box;flex:1 1 auto;min-width:180px;height:44px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg,10px);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;}",
4998
+ ".dkp-addbtn:hover{background:var(--dsw-alias-interactive-bg-hover);}",
4999
+ ".dkp-addcard{flex:1 1 auto;padding:14px 16px;}",
5000
+ ".dkp-addcard .dkp-editor{background:transparent;padding:0;}",
5001
+ ".dkp-editor-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px;}",
5002
+ ".dkp-editor-actions .dkp-hint{margin-right:auto;}",
5003
+ "@media (max-width:680px){.dkp-page{max-width:none;gap:14px}.dkp-intro{font-size:13px;line-height:20px}.dkp-model-row{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.dkp-model-row .dkp-iconbtn{justify-self:end}.dkp-editor{padding:12px}.dkp-addcard{padding:12px 14px}.dkp-btn{height:40px}.dkp-btn.sm{height:34px}.dkp-addbtn{height:48px}}"
4254
5004
  ].join("\n"),
4255
5005
  View: ModelsView,
4256
5006
  HomeStat: ModelsStat
@@ -4402,7 +5152,7 @@ var balanceStore = {
4402
5152
  load() {
4403
5153
  if (this.snap.loading) return;
4404
5154
  this.set({ loading: true });
4405
- fetch("/dsh-dock/balance", { signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u4F59\u989D\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
5155
+ fetch("dsh-dock/balance", { signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u4F59\u989D\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
4406
5156
  }
4407
5157
  };
4408
5158
  function useBalance(ctx) {
@@ -4725,7 +5475,7 @@ var feature5 = {
4725
5475
  var import_react8 = require("react");
4726
5476
  var import_jsx_runtime2 = require("react/jsx-runtime");
4727
5477
  function rpcCall2(method, args) {
4728
- return fetch("/dsh-dock/animation/" + method, {
5478
+ return fetch("dsh-dock/animation/" + method, {
4729
5479
  method: "POST",
4730
5480
  headers: { "content-type": "application/json" },
4731
5481
  body: JSON.stringify(args === void 0 ? {} : args)
@@ -6402,7 +7152,7 @@ var feature6 = {
6402
7152
  var import_react9 = require("react");
6403
7153
  var import_jsx_runtime3 = require("react/jsx-runtime");
6404
7154
  function rpcCall3(method, args) {
6405
- return fetch("/dsh-dock/notify/" + method, {
7155
+ return fetch("dsh-dock/notify/" + method, {
6406
7156
  method: "POST",
6407
7157
  headers: { "content-type": "application/json" },
6408
7158
  body: JSON.stringify(args === void 0 ? {} : args)
@@ -7166,7 +7916,7 @@ var feature7 = {
7166
7916
  var import_react10 = require("react");
7167
7917
  var import_jsx_runtime4 = require("react/jsx-runtime");
7168
7918
  function rpcCall4(method, args) {
7169
- return fetch("/dsh-dock/runstate/" + method, {
7919
+ return fetch("dsh-dock/runstate/" + method, {
7170
7920
  method: "POST",
7171
7921
  headers: { "content-type": "application/json" },
7172
7922
  body: JSON.stringify(args === void 0 ? {} : args)
@@ -10149,7 +10899,7 @@ function initialDash() {
10149
10899
  }
10150
10900
  function useTaskPulse() {
10151
10901
  const [status, setStatus] = (0, import_react20.useState)({ loading: true, active: [], error: false });
10152
- const refresh = (0, import_react20.useCallback)(() => fetch("/dsh-dock/animation/status", {
10902
+ const refresh = (0, import_react20.useCallback)(() => fetch("dsh-dock/animation/status", {
10153
10903
  method: "POST",
10154
10904
  headers: { "content-type": "application/json" },
10155
10905
  body: "{}"
@@ -10922,7 +11672,7 @@ var import_react21 = require("react");
10922
11672
  var import_qrcode = __toESM(require_browser(), 1);
10923
11673
  var import_jsx_runtime14 = require("react/jsx-runtime");
10924
11674
  function rpc(method, payload) {
10925
- return fetch("/dsh-dock/mobile-relay/" + method, {
11675
+ return fetch("dsh-dock/mobile-relay/" + method, {
10926
11676
  method: "POST",
10927
11677
  headers: { "content-type": "application/json" },
10928
11678
  body: JSON.stringify(payload || {})
@@ -11221,247 +11971,6 @@ function RemoteCard() {
11221
11971
  function MobileRelayView() {
11222
11972
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RemoteCard, {});
11223
11973
  }
11224
- function MobileRelayOverlay() {
11225
- (0, import_react21.useEffect)(() => {
11226
- if (typeof window === "undefined" || typeof document === "undefined") return void 0;
11227
- const host = window.location && window.location.hostname || "";
11228
- const loopback = host === "127.0.0.1" || host === "localhost" || host === "::1" || host.endsWith(".localhost");
11229
- if (loopback) return void 0;
11230
- if (typeof MutationObserver === "undefined") return void 0;
11231
- let done = false;
11232
- const tryDismiss = () => {
11233
- if (done) return;
11234
- const dialogs = document.querySelectorAll('[role="dialog"]');
11235
- for (const dialog of dialogs) {
11236
- const text = dialog.textContent || "";
11237
- if (!/预览版说明|Preview Notice/.test(text)) continue;
11238
- const btn = [...dialog.querySelectorAll("button")].find((b) => {
11239
- const t = (b.textContent || "").trim();
11240
- return t === "\u7EE7\u7EED" || t === "Continue";
11241
- });
11242
- if (btn) {
11243
- done = true;
11244
- btn.click();
11245
- observer.disconnect();
11246
- }
11247
- break;
11248
- }
11249
- };
11250
- const observer = new MutationObserver(tryDismiss);
11251
- observer.observe(document.body, { childList: true, subtree: true });
11252
- tryDismiss();
11253
- const stopTimer = setTimeout(() => observer.disconnect(), 6e4);
11254
- return () => {
11255
- clearTimeout(stopTimer);
11256
- observer.disconnect();
11257
- };
11258
- }, []);
11259
- (0, import_react21.useEffect)(() => {
11260
- if (typeof window === "undefined" || typeof document === "undefined") return void 0;
11261
- const mq = window.matchMedia ? window.matchMedia("(max-width:700px)") : null;
11262
- if (!mq) return void 0;
11263
- if (window.__dshDockMobileDrawer === "host") return void 0;
11264
- window.__dshDockMobileDrawer = "client";
11265
- const narrow = () => mq.matches;
11266
- const frameEl = () => document.querySelector('[class$="frame"]');
11267
- const sidebarCollapsed = () => {
11268
- const f = frameEl();
11269
- return !!f && f.hasAttribute("data-sidebar-collapsed");
11270
- };
11271
- const dialogOpen = () => !!document.querySelector('[role="dialog"][class*="panel"]');
11272
- const rightOpen = () => !!document.querySelector("[data-sidebar-right-panel][data-sidebar-right-open]");
11273
- const collapse = () => {
11274
- const b = document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
11275
- if (b) b.click();
11276
- };
11277
- const expand = () => {
11278
- const b = document.querySelector('button[aria-label="\u6253\u5F00\u4FA7\u8FB9\u680F"],button[aria-label="Open sidebar"]');
11279
- if (b) b.click();
11280
- };
11281
- const closeDetails = () => {
11282
- const panel = document.querySelector("[data-sidebar-right-panel][data-sidebar-right-open]");
11283
- if (!panel) return;
11284
- const toggle = panel.querySelector("[data-sidebar-right-toggle]");
11285
- if (toggle) toggle.click();
11286
- };
11287
- const scrim = document.createElement("div");
11288
- scrim.className = "dsh-mobile-scrim";
11289
- document.body.appendChild(scrim);
11290
- document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => el.remove());
11291
- const tabbar = document.createElement("nav");
11292
- tabbar.className = "dsh-mobile-tabbar";
11293
- tabbar.setAttribute("aria-label", "\u5E95\u90E8\u5BFC\u822A");
11294
- let tasksActive = 0;
11295
- let tasksWaiting = 0;
11296
- const tabDefs = [
11297
- {
11298
- id: "sessions",
11299
- label: "\u4F1A\u8BDD",
11300
- icon: '<svg viewBox="0 0 24 24"><path d="M21 12a8 8 0 0 1-8 8H5l-2 2V12a8 8 0 0 1 8-8h2a8 8 0 0 1 8 8z"/></svg>',
11301
- act: () => expand()
11302
- },
11303
- {
11304
- id: "dock",
11305
- label: "\u63D2\u4EF6",
11306
- icon: '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>',
11307
- act: () => {
11308
- openPanel(tasksActive > 0 || tasksWaiting > 0 ? "runstate" : "home");
11309
- }
11310
- },
11311
- {
11312
- id: "settings",
11313
- label: "\u8BBE\u7F6E",
11314
- icon: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.23.6.86 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>',
11315
- act: () => {
11316
- const b = [...document.querySelectorAll("button")].find((x) => (x.getAttribute("aria-label") || "") === "\u8BBE\u7F6E" || (x.textContent || "").trim() === "\u8BBE\u7F6E");
11317
- if (b) b.click();
11318
- }
11319
- }
11320
- ];
11321
- let tasksBadge = null;
11322
- const tabButtons = tabDefs.map((def) => {
11323
- const b = document.createElement("button");
11324
- b.type = "button";
11325
- b.className = "dsh-mobile-tab";
11326
- b.dataset.tab = def.id;
11327
- b.innerHTML = def.icon + "<span>" + def.label + "</span>";
11328
- b.addEventListener("click", () => def.act());
11329
- if (def.id === "dock") {
11330
- tasksBadge = document.createElement("span");
11331
- tasksBadge.className = "dsh-mobile-tab-badge";
11332
- tasksBadge.style.display = "none";
11333
- b.appendChild(tasksBadge);
11334
- }
11335
- tabbar.appendChild(b);
11336
- return b;
11337
- });
11338
- document.body.appendChild(tabbar);
11339
- const syncTabs = () => {
11340
- const expanded = !sidebarCollapsed();
11341
- const settingsOpen = dialogOpen();
11342
- for (const b of tabButtons) {
11343
- const id = b.dataset.tab;
11344
- const on = id === "sessions" && expanded || id === "dock" && panelNav.open || id === "settings" && settingsOpen;
11345
- b.classList.toggle("on", on);
11346
- }
11347
- };
11348
- const unsubPanel = subscribePanel(syncTabs);
11349
- let runstateGone = false;
11350
- const pollTasks = () => {
11351
- if (runstateGone || !tasksBadge || !narrow() || document.visibilityState === "hidden" || !tabbar.isConnected) return;
11352
- fetch("/dsh-dock/runstate/status", {
11353
- method: "POST",
11354
- headers: { "content-type": "application/json" },
11355
- body: "{}"
11356
- }).then(async (res) => {
11357
- if (res.status === 404 || res.status === 405) {
11358
- runstateGone = true;
11359
- tasksActive = 0;
11360
- tasksWaiting = 0;
11361
- tasksBadge.style.display = "none";
11362
- return null;
11363
- }
11364
- const body = await res.json().catch(() => null);
11365
- if (!body) return null;
11366
- const active = body.data && body.data.active || [];
11367
- const waiting = active.filter((t) => t.approvals && t.approvals.length).length;
11368
- tasksActive = active.length;
11369
- tasksWaiting = waiting;
11370
- if (waiting > 0) {
11371
- tasksBadge.textContent = String(waiting);
11372
- tasksBadge.className = "dsh-mobile-tab-badge wait";
11373
- tasksBadge.style.display = "";
11374
- } else if (active.length > 0) {
11375
- tasksBadge.textContent = String(active.length);
11376
- tasksBadge.className = "dsh-mobile-tab-badge";
11377
- tasksBadge.style.display = "";
11378
- } else {
11379
- tasksBadge.style.display = "none";
11380
- }
11381
- }).catch(() => {
11382
- });
11383
- };
11384
- const tasksTimer = setInterval(pollTasks, 3e3);
11385
- const vv = window.visualViewport || null;
11386
- const kbdSync = () => {
11387
- if (!vv) return;
11388
- const kbd = vv.height > 0 && window.innerHeight - vv.height > 120;
11389
- document.documentElement.classList.toggle("dsh-dock-kbd", kbd);
11390
- };
11391
- const sync = () => {
11392
- kbdSync();
11393
- document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => {
11394
- if (el !== tabbar) el.remove();
11395
- });
11396
- const on = narrow() && !!frameEl() && !dialogOpen();
11397
- const collapsed = sidebarCollapsed();
11398
- const right = rightOpen();
11399
- if (scrim) scrim.style.display = on && !collapsed ? "" : "none";
11400
- tabbar.style.display = narrow() && !!frameEl() && !panelNav.open && !right ? "" : "none";
11401
- syncTabs();
11402
- };
11403
- sync();
11404
- pollTasks();
11405
- const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(sync) : null;
11406
- if (observer) observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-sidebar-collapsed"], subtree: true });
11407
- const timer = setInterval(sync, 1500);
11408
- const onMq = () => sync();
11409
- if (mq.addEventListener) mq.addEventListener("change", onMq);
11410
- else if (mq.addListener) mq.addListener(onMq);
11411
- window.addEventListener("resize", kbdSync);
11412
- if (vv && vv.addEventListener) vv.addEventListener("resize", kbdSync);
11413
- const onVisible = () => {
11414
- if (document.visibilityState === "visible") pollTasks();
11415
- };
11416
- document.addEventListener("visibilitychange", onVisible);
11417
- const onClick = (e) => {
11418
- if (!narrow()) return;
11419
- const t = e.target;
11420
- if (!t || !t.closest) return;
11421
- if (t.closest(".dsh-mobile-tabbar")) return;
11422
- const expanded = !!document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
11423
- const col = document.querySelector('[class*="sidebarCol"]');
11424
- const inSidebar = col && col.contains(t);
11425
- if (!expanded && !inSidebar) return;
11426
- if (inSidebar) {
11427
- const row = t.closest('[role="treeitem"]');
11428
- const rowKey = row && row.getAttribute("data-row-key") || "";
11429
- const sessionRow = rowKey && rowKey.startsWith("session:") || row && !rowKey && !row.querySelector('[role="treeitem"]') && row.closest('[role="tree"]');
11430
- const fresh = t.closest('[class*="newSession"]');
11431
- if (sessionRow || fresh) setTimeout(() => {
11432
- collapse();
11433
- closeDetails();
11434
- }, 300);
11435
- else {
11436
- setTimeout(() => {
11437
- if (document.querySelector('[role="dialog"]')) collapse();
11438
- }, 260);
11439
- }
11440
- return;
11441
- }
11442
- if (t.closest('[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[data-sidebar-right-panel]')) return;
11443
- collapse();
11444
- };
11445
- document.addEventListener("click", onClick, true);
11446
- return () => {
11447
- document.removeEventListener("click", onClick, true);
11448
- if (observer) observer.disconnect();
11449
- clearInterval(timer);
11450
- clearInterval(tasksTimer);
11451
- unsubPanel();
11452
- if (mq.removeEventListener) mq.removeEventListener("change", onMq);
11453
- else if (mq.removeListener) mq.removeListener(onMq);
11454
- window.removeEventListener("resize", kbdSync);
11455
- if (vv && vv.removeEventListener) vv.removeEventListener("resize", kbdSync);
11456
- document.removeEventListener("visibilitychange", onVisible);
11457
- document.documentElement.classList.remove("dsh-dock-kbd");
11458
- if (scrim) scrim.remove();
11459
- tabbar.remove();
11460
- if (window.__dshDockMobileDrawer === "client") delete window.__dshDockMobileDrawer;
11461
- };
11462
- }, []);
11463
- return null;
11464
- }
11465
11974
  function MobileRelayHomeStat() {
11466
11975
  const [summary, setSummary] = (0, import_react21.useState)("\u672A\u5F00\u542F\uFF0C\u5F00\u542F\u540E\u53EF\u8FDC\u7A0B\u767B\u5F55");
11467
11976
  (0, import_react21.useEffect)(() => {
@@ -11487,53 +11996,9 @@ var feature10 = {
11487
11996
  defaultEnabled: false,
11488
11997
  css: `
11489
11998
  .dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
11490
- /* \u62BD\u5C49\u906E\u7F69\uFF1A\u4FA7\u680F\u6D6E\u5C42\u5C55\u5F00\u65F6\u76D6\u4F4F\u9875\u9762\u3002\u5DE6\u7F18\u6D6E\u52A8\u300C\u62BD\u5C49\u628A\u624B\u300D\u5DF2\u79FB\u9664\u2014\u2014\u4F1A\u8BDD\u5165\u53E3
11491
- * \u7EDF\u4E00\u8D70\u5E95\u90E8 Tab \u680F\u300C\u4F1A\u8BDD\u300D\u9875\u7B7E\uFF0C\u628A\u624B\u60AC\u6D6E\u5728\u5185\u5BB9\u4E0A\u8FD8\u538B\u8FC7\u8F93\u5165\u533A\uFF08\u7528\u6237\u5B9E\u6D4B\uFF09\u3002 */
11492
- @media (max-width:700px){.dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}
11493
- /* ---- \u4EFF ZCode \u624B\u673A\u7AEF\uFF1A\u5E95\u90E8 Tab \u680F + \u8F93\u5165\u533A\u5438\u9644\u5E95\u90E8 ---- */
11494
- .dsh-mobile-tabbar{position:fixed;left:0;right:0;bottom:0;z-index:65;display:flex;align-items:stretch;justify-content:space-around;padding:6px 8px calc(8px + env(safe-area-inset-bottom,0px));background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 92%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.25));touch-action:manipulation}
11495
- .dsh-mobile-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:48px;padding:4px 0;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#8b95ab);font:600 11px/1.2 inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:10px}
11496
- .dsh-mobile-tab svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
11497
- .dsh-mobile-tab.on{color:var(--dsw-alias-label-primary,#e6eaf2)}
11498
- .dsh-mobile-tab.on svg{color:var(--dk-accent,#4d9fff)}
11499
- .dsh-mobile-tab:active{transform:scale(.94)}
11500
- /* \u300C\u4EFB\u52A1\u300D\u9875\u7B7E\u89D2\u6807\uFF1A\u8FDB\u884C\u4E2D=\u84DD\uFF08\u6570\u91CF\uFF09\uFF0C\u7B49\u5F85\u786E\u8BA4=\u7EA2\uFF08\u6570\u91CF+\u8109\u51B2\uFF09\u2014\u2014\u63A5\u529B\u573A\u666F\u91CC
11501
- * \u7B49\u5F85\u786E\u8BA4\u662F\u6700\u9700\u8981\u4EBA\u4ECB\u5165\u7684\u72B6\u6001\uFF0C\u4E00\u773C\u53EF\u89C1\uFF1B\u6570\u636E\u6765\u81EA runstate \u5BBF\u4E3B\u8DEF\u7531\u4F4E\u9891\u8F6E\u8BE2\u3002 */
11502
- .dsh-mobile-tab{position:relative}
11503
- .dsh-mobile-tab-badge{position:absolute;top:0;right:calc(50% - 26px);box-sizing:border-box;min-width:16px;height:16px;padding:0 4px;border-radius:9px;display:flex;align-items:center;justify-content:center;background:#4d9fff;color:#fff;font-size:10px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;box-shadow:0 2px 8px rgba(0,0,0,.35);pointer-events:none}
11504
- .dsh-mobile-tab-badge.wait{background:#ef4444;animation:dsh-badge-pulse 1.2s ease-in-out infinite}
11505
- @keyframes dsh-badge-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}
11506
- @media (prefers-reduced-motion:reduce){.dsh-mobile-tab-badge.wait{animation:none}}
11507
- /* \u8F93\u5165\u533A\u5438\u9644\u5230 Tab \u680F\u4E0A\u65B9\uFF1A\u5BBF\u4E3B\u628A\u6B22\u8FCE\u5185\u5BB9 justify-content:center \u5782\u76F4\u5C45\u4E2D\uFF0C
11508
- * \u624B\u673A\u89C6\u53E3\u4E0B\u8F93\u5165\u5361\u7247\u60AC\u5728\u4E2D\u90E8\u3001\u4E0B\u65B9\u7559\u51FA\u5927\u7247\u7A7A\u767D\u3002\u6539\u7528\u300C\u8F93\u5165\u533A margin-top:auto\u300D
11509
- * \u8D34\u5E95\uFF0C\u5E76\u7ED9\u6EDA\u52A8\u4F53\u7559\u51FA Tab \u680F\u9AD8\u5EA6\u7684\u5185\u8FB9\u8DDD\uFF0C\u907F\u514D\u5185\u5BB9\u88AB Tab \u680F\u906E\u6321\u3002
11510
- * \u26A0\uFE0F \u7EDD\u4E0D\u80FD\u7ED9\u6EDA\u52A8\u4F53\u52A0 justify-content:flex-end/center \u8D34\u5E95\uFF1A\u5185\u5BB9\u4F1A\u88AB\u6324\u5230\u6EDA\u52A8\u4F53
11511
- * \u8D77\u70B9\u4E4B\u5916\uFF0CChrome \u4E0D\u628A\u8D77\u70B9\u65B9\u5411\u6EA2\u51FA\u7B97\u8FDB scrollHeight\uFF08\u624B\u673A\u89C6\u53E3\u5B9E\u6D4B
11512
- * scrollHeight==clientHeight\u3001maxScroll==0\uFF09\uFF0C\u4F1A\u8BDD\u8BB0\u5F55\u5F7B\u5E95\u6EDA\u4E0D\u52A8\u3001\u9996\u6761\u6D88\u606F\u6C38\u8FDC
11513
- * \u770B\u4E0D\u5230\u3002\u5BBF\u4E3B\u534A\u90E8\u65E7\u7248\u6CE8\u5165\u91CC\u8FD8\u7559\u7740\u90A3\u6761 flex-end\uFF08\u8981\u91CD\u542F dsh web \u624D\u6D88\u5931\uFF09\uFF0C\u8FD9\u91CC
11514
- * \u7528\u66F4\u9AD8\u4F18\u5148\u7EA7\u7684\u9009\u62E9\u5668\u663E\u5F0F\u590D\u4F4D\uFF1A\u5237\u65B0\u9875\u9762\u5373\u4FEE\u597D\uFF0C\u4E0D\u5FC5\u7B49\u91CD\u542F\u3002 */
11515
- [class*="scrollBody"][data-conversation-scroll]{justify-content:flex-start !important;padding-bottom:calc(72px + env(safe-area-inset-bottom,0px)) !important}
11516
- [class*="scrollBody"]{padding-bottom:calc(72px + env(safe-area-inset-bottom,0px)) !important}
11517
- [class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:6px}
11518
- /* \u8F93\u5165\u533A\u5DE5\u5177\u884C\u4E0A\u7684 dsh-dock chip\uFF08\u7528\u91CF/\u4F59\u989D\uFF09\uFF1A\u7A84\u5C4F\u4E0B 28cqw \u7684\u5BBD\u5EA6\u4E0A\u9650\u628A\u5B83\u4EEC\u6324\u6210
11519
- * \u300C\u2026\u300D\u300C\u4F59..\u300D\uFF08\u624B\u673A\u5B9E\u6D4B\uFF09\uFF0C\u65E2\u8BFB\u4E0D\u51FA\u6570\u503C\u53C8\u767D\u5360\u8F93\u5165\u884C\u7A7A\u95F4\u3002\u624B\u673A\u7AEF\u6574\u7EC4\u6536\u8D77\uFF0C\u6570\u503C\u5728
11520
- * \u5E95\u90E8\u300C\u529F\u80FD\u575E\u300D\u91CC\u770B\uFF08\u7528\u91CF/\u4F59\u989D\u9875\u4FE1\u606F\u66F4\u5168\uFF09\uFF1B\u684C\u9762\u7AEF\u4E0D\u52A8\u3002 */
11521
- .dockchip-row{display:none}
11522
- /* \u6536\u8D77\u7684\u4FA7\u680F\u8F68\u9053\uFF08collapsed rail\uFF09\u5728\u7A84\u5C4F\u53EA\u5269 1px \u5360\u4F4D\uFF0C\u4F46\u8F68\u9053\u91CC\u7684\u6309\u94AE
11523
- * \u4ECD\u4F1A\u6EA2\u51FA\u8D34\u5728\u5DE6\u8FB9\u7F18\uFF08\u529F\u80FD\u575E/\u8FDB\u5316/\u8BBE\u7F6E\u4E09\u679A\u9732\u51FA\u534A\u8FB9\u7684\u5706\u94AE\uFF09\u3002\u9690\u85CF\u6574\u5217\uFF0C
11524
- * \u8FD9\u4E9B\u5165\u53E3\u5DF2\u7531\u5E95\u90E8 Tab \u680F\u63A5\u7BA1\uFF1B\u5C55\u5F00\u62BD\u5C49\uFF08z 80\uFF09\u4E0D\u53D7\u5F71\u54CD\u3002 */
11525
- [class*="sidebarCol"]:has([class*="collapsed"]){visibility:hidden}
11526
- /* \u8F6F\u952E\u76D8\u5F39\u51FA\uFF08visualViewport \u5224\u5B9A\uFF0C\u6302 html.dsh-dock-kbd\uFF09\uFF1ATab \u680F\u6574\u4F53\u8BA9\u4F4D\uFF08\u6253\u5B57\u65F6
11527
- * \u5B83\u53EA\u4F1A\u6324\u5360\u952E\u76D8\u4E0A\u65B9\u7684\u4E00\u7EBF\u7A7A\u95F4\uFF09\uFF0C\u6EDA\u52A8\u4F53\u7559\u767D\u6536\u7A84\u3002\u9009\u62E9\u5668\u7279\u5F02\u6027\u9AD8\u4E8E\u4E0A\u9762\u7684 72px
11528
- * \u7559\u767D\u89C4\u5219\uFF0C\u80FD\u538B\u8FC7\u53BB\u3002\u5BBF\u4E3B\u63A5\u7BA1\u671F\u95F4\u7531\u5BBF\u4E3B\u811A\u672C\u540C\u6B65\u7F6E/\u6458\u8FD9\u4E2A\u7C7B\u3002 */
11529
- html.dsh-dock-kbd .dsh-mobile-tabbar{display:none !important}
11530
- html.dsh-dock-kbd [class*="scrollBody"]{padding-bottom:12px !important}
11531
- /* \u529F\u80FD\u575E\u9762\u677F\u5728\u624B\u673A\u7AEF\u62AC\u5230\u539F\u751F\u5F39\u5C42\u4E4B\u4E0A\uFF1A\u4ECE\u4FA7\u680F\u8FDB\u7684\u5B98\u65B9\u63D2\u4EF6\u7BA1\u7406\u9875\u7B49\u539F\u751F\u5F39\u5C42 z \u5E8F\u9AD8\u4E8E
11532
- * \u9762\u677F\u9ED8\u8BA4\u7684 200\uFF0C\u529F\u80FD\u575E\u5F00\u5728\u4E0B\u9762\u4F1A\u88AB\u76D6\u4F4F\u2014\u2014\u300C\u63D2\u4EF6\u300D\u9875\u7B7E\u70B9\u4E86\u50CF\u6CA1\u53CD\u5E94/\u9732\u51FA\u539F\u9875\u9762\u3002 */
11533
- [class*="dockm-backdrop"]{z-index:2000 !important}}`,
11999
+ `,
11534
12000
  View: MobileRelayView,
11535
- HomeStat: MobileRelayHomeStat,
11536
- Overlay: MobileRelayOverlay
12001
+ HomeStat: MobileRelayHomeStat
11537
12002
  };
11538
12003
 
11539
12004
  // features/mobile/view.js
@@ -11541,54 +12006,36 @@ var import_react22 = __toESM(require("react"), 1);
11541
12006
  var import_react23 = require("react");
11542
12007
  var MOBILE_QUERY_NARROW = "(max-width: 820px)";
11543
12008
  var MOBILE_QUERY_COARSE = "(pointer: coarse) and (hover: none)";
11544
- var MOBILE_STORE_KEY = "dsh-dock/mobile/v1";
11545
- var mobilePref = { floating: true };
11546
- try {
11547
- if (typeof localStorage !== "undefined") {
11548
- const raw = localStorage.getItem(MOBILE_STORE_KEY);
11549
- const obj = raw ? JSON.parse(raw) : null;
11550
- if (obj && typeof obj === "object" && typeof obj.floating === "boolean") mobilePref.floating = obj.floating;
11551
- }
11552
- } catch {
11553
- }
11554
- var prefListeners = /* @__PURE__ */ new Set();
11555
- function notifyPref() {
11556
- for (const fn of prefListeners) {
11557
- try {
11558
- fn();
11559
- } catch {
11560
- }
11561
- }
11562
- }
11563
- function floatingShown() {
11564
- return mobilePref.floating !== false;
11565
- }
11566
- function setFloatingShown(value) {
11567
- mobilePref.floating = value !== false;
12009
+ var MOBILE_QUERY_CHROME = "(max-width: 700px)";
12010
+ function mobileActive() {
12011
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
11568
12012
  try {
11569
- if (typeof localStorage !== "undefined") localStorage.setItem(MOBILE_STORE_KEY, JSON.stringify(mobilePref));
12013
+ return window.matchMedia(MOBILE_QUERY_NARROW).matches || window.matchMedia(MOBILE_QUERY_COARSE).matches;
11570
12014
  } catch {
12015
+ return false;
11571
12016
  }
11572
- notifyPref();
11573
12017
  }
11574
- function subscribePref(fn) {
11575
- prefListeners.add(fn);
11576
- return () => {
11577
- prefListeners.delete(fn);
12018
+ function watchQuery(query, onChange) {
12019
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
11578
12020
  };
11579
- }
11580
- function mobileActive() {
11581
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
12021
+ let mq = null;
11582
12022
  try {
11583
- return window.matchMedia(MOBILE_QUERY_NARROW).matches || window.matchMedia(MOBILE_QUERY_COARSE).matches;
12023
+ mq = window.matchMedia(query);
11584
12024
  } catch {
11585
- return false;
12025
+ return () => {
12026
+ };
11586
12027
  }
12028
+ const update = () => onChange(mq.matches);
12029
+ update();
12030
+ if (typeof mq.addEventListener === "function") mq.addEventListener("change", update);
12031
+ else if (typeof mq.addListener === "function") mq.addListener(update);
12032
+ return () => {
12033
+ if (typeof mq.removeEventListener === "function") mq.removeEventListener("change", update);
12034
+ else if (typeof mq.removeListener === "function") mq.removeListener(update);
12035
+ };
11587
12036
  }
11588
- function MobileOverlay(props) {
11589
- const ctx = props && props.ctx;
12037
+ function MobileOverlay() {
11590
12038
  const [active, setActive] = (0, import_react23.useState)(() => mobileActive());
11591
- const [floating, setFloating] = (0, import_react23.useState)(() => floatingShown());
11592
12039
  (0, import_react23.useEffect)(() => {
11593
12040
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
11594
12041
  let mqs = [];
@@ -11628,56 +12075,217 @@ function MobileOverlay(props) {
11628
12075
  document.body.removeAttribute("data-dk-mobile");
11629
12076
  };
11630
12077
  }, [active]);
11631
- (0, import_react23.useEffect)(() => subscribePref(() => setFloating(floatingShown())), []);
11632
- const [relayBand, setRelayBand] = (0, import_react23.useState)(() => {
11633
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
11634
- try {
11635
- return window.matchMedia("(max-width: 700px)").matches;
11636
- } catch {
11637
- return false;
12078
+ (0, import_react23.useEffect)(() => mobileChrome(), []);
12079
+ return null;
12080
+ }
12081
+ function mobileChrome() {
12082
+ if (typeof window === "undefined" || typeof document === "undefined") return void 0;
12083
+ const mq = window.matchMedia ? window.matchMedia(MOBILE_QUERY_CHROME) : null;
12084
+ if (!mq) return void 0;
12085
+ if (window.__dshDockMobileDrawer === "host") return void 0;
12086
+ window.__dshDockMobileDrawer = "client";
12087
+ const narrow = () => mq.matches;
12088
+ const frameEl = () => document.querySelector('[class$="frame"]');
12089
+ const sidebarCollapsed = () => {
12090
+ const f = frameEl();
12091
+ return !!f && f.hasAttribute("data-sidebar-collapsed");
12092
+ };
12093
+ const dialogOpen = () => !!document.querySelector('[role="dialog"][class*="panel"]');
12094
+ const rightOpen = () => !!document.querySelector("[data-sidebar-right-panel][data-sidebar-right-open]");
12095
+ const collapse = () => {
12096
+ const b = document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
12097
+ if (b) b.click();
12098
+ };
12099
+ const expand = () => {
12100
+ const b = document.querySelector('button[aria-label="\u6253\u5F00\u4FA7\u8FB9\u680F"],button[aria-label="Open sidebar"]');
12101
+ if (b) b.click();
12102
+ };
12103
+ const closeDetails = () => {
12104
+ const panel = document.querySelector("[data-sidebar-right-panel][data-sidebar-right-open]");
12105
+ if (!panel) return;
12106
+ const toggle = panel.querySelector("[data-sidebar-right-toggle]");
12107
+ if (toggle) toggle.click();
12108
+ };
12109
+ const scrim = document.createElement("div");
12110
+ scrim.className = "dsh-mobile-scrim";
12111
+ document.body.appendChild(scrim);
12112
+ document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => el.remove());
12113
+ const tabbar = document.createElement("nav");
12114
+ tabbar.className = "dsh-mobile-tabbar";
12115
+ tabbar.setAttribute("aria-label", "\u5E95\u90E8\u5BFC\u822A");
12116
+ let tasksActive = 0;
12117
+ let tasksWaiting = 0;
12118
+ const tabDefs = [
12119
+ {
12120
+ id: "sessions",
12121
+ label: "\u4F1A\u8BDD",
12122
+ icon: '<svg viewBox="0 0 24 24"><path d="M21 12a8 8 0 0 1-8 8H5l-2 2V12a8 8 0 0 1 8-8h2a8 8 0 0 1 8 8z"/></svg>',
12123
+ act: () => expand()
12124
+ },
12125
+ {
12126
+ id: "dock",
12127
+ label: "\u63D2\u4EF6",
12128
+ icon: '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>',
12129
+ act: () => {
12130
+ openPanel(tasksActive > 0 || tasksWaiting > 0 ? "runstate" : "home");
12131
+ }
12132
+ },
12133
+ {
12134
+ id: "settings",
12135
+ label: "\u8BBE\u7F6E",
12136
+ icon: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.23.6.86 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>',
12137
+ act: () => {
12138
+ const b = [...document.querySelectorAll("button")].find((x) => {
12139
+ const a = x.getAttribute("aria-label") || "";
12140
+ return a === "\u8BBE\u7F6E" || a === "Settings" || (x.textContent || "").trim() === "\u8BBE\u7F6E";
12141
+ });
12142
+ if (b) b.click();
12143
+ }
11638
12144
  }
12145
+ ];
12146
+ let tasksBadge = null;
12147
+ const tabButtons = tabDefs.map((def) => {
12148
+ const b = document.createElement("button");
12149
+ b.type = "button";
12150
+ b.className = "dsh-mobile-tab";
12151
+ b.dataset.tab = def.id;
12152
+ b.innerHTML = def.icon + "<span>" + def.label + "</span>";
12153
+ b.addEventListener("click", () => def.act());
12154
+ if (def.id === "dock") {
12155
+ tasksBadge = document.createElement("span");
12156
+ tasksBadge.className = "dsh-mobile-tab-badge";
12157
+ tasksBadge.style.display = "none";
12158
+ b.appendChild(tasksBadge);
12159
+ }
12160
+ tabbar.appendChild(b);
12161
+ return b;
11639
12162
  });
11640
- (0, import_react23.useEffect)(() => {
11641
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
11642
- let mq = null;
11643
- try {
11644
- mq = window.matchMedia("(max-width: 700px)");
11645
- } catch {
11646
- return void 0;
12163
+ document.body.appendChild(tabbar);
12164
+ const syncTabs = () => {
12165
+ const expanded = !sidebarCollapsed();
12166
+ const settingsOpen = dialogOpen();
12167
+ for (const b of tabButtons) {
12168
+ const id = b.dataset.tab;
12169
+ const on = id === "sessions" && expanded || id === "dock" && panelNav.open || id === "settings" && settingsOpen;
12170
+ b.classList.toggle("on", on);
11647
12171
  }
11648
- const update = () => setRelayBand(mq.matches);
11649
- update();
11650
- if (typeof mq.addEventListener === "function") mq.addEventListener("change", update);
11651
- else if (typeof mq.addListener === "function") mq.addListener(update);
11652
- return () => {
11653
- if (typeof mq.removeEventListener === "function") mq.removeEventListener("change", update);
11654
- else if (typeof mq.removeListener === "function") mq.removeListener(update);
11655
- };
11656
- }, []);
11657
- if (!active || !floating) return null;
11658
- const relayChromeActive = typeof window !== "undefined" && window.__dshDockMobileDrawer != null && relayBand;
11659
- if (relayChromeActive) return null;
11660
- const onToggle = () => {
11661
- try {
11662
- const layout = ctx && ctx.get ? ctx.get("layout") : null;
11663
- if (layout && typeof layout.toggleSidebar === "function") layout.toggleSidebar();
11664
- else console.warn("[dsh-dock] \u624B\u673A\u9002\u914D\uFF1Actx.layout.toggleSidebar \u4E0D\u53EF\u7528\uFF08dsh \u7248\u672C\u8FC7\u65E7\uFF1F\uFF09");
11665
- } catch (err) {
11666
- console.error("[dsh-dock] \u624B\u673A\u9002\u914D\uFF1A\u5207\u6362\u4FA7\u680F\u5931\u8D25\uFF1A", err);
12172
+ };
12173
+ const unsubPanel = subscribePanel(syncTabs);
12174
+ let runstateGone = false;
12175
+ const pollTasks = () => {
12176
+ if (runstateGone || !tasksBadge || !narrow() || document.visibilityState === "hidden" || !tabbar.isConnected) return;
12177
+ fetch("dsh-dock/runstate/status", {
12178
+ method: "POST",
12179
+ headers: { "content-type": "application/json" },
12180
+ body: "{}"
12181
+ }).then(async (res) => {
12182
+ if (res.status === 404 || res.status === 405) {
12183
+ runstateGone = true;
12184
+ tasksActive = 0;
12185
+ tasksWaiting = 0;
12186
+ tasksBadge.style.display = "none";
12187
+ return null;
12188
+ }
12189
+ const body = await res.json().catch(() => null);
12190
+ if (!body) return null;
12191
+ const active = body.data && body.data.active || [];
12192
+ const waiting = active.filter((t) => t.approvals && t.approvals.length).length;
12193
+ tasksActive = active.length;
12194
+ tasksWaiting = waiting;
12195
+ if (waiting > 0) {
12196
+ tasksBadge.textContent = String(waiting);
12197
+ tasksBadge.className = "dsh-mobile-tab-badge wait";
12198
+ tasksBadge.style.display = "";
12199
+ } else if (active.length > 0) {
12200
+ tasksBadge.textContent = String(active.length);
12201
+ tasksBadge.className = "dsh-mobile-tab-badge";
12202
+ tasksBadge.style.display = "";
12203
+ } else {
12204
+ tasksBadge.style.display = "none";
12205
+ }
12206
+ }).catch(() => {
12207
+ });
12208
+ };
12209
+ const tasksTimer = setInterval(pollTasks, 3e3);
12210
+ const vv = window.visualViewport || null;
12211
+ const kbdSync = () => {
12212
+ if (!vv) return;
12213
+ const kbd = vv.height > 0 && window.innerHeight - vv.height > 120;
12214
+ document.documentElement.classList.toggle("dsh-dock-kbd", kbd);
12215
+ };
12216
+ const sync = () => {
12217
+ kbdSync();
12218
+ document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => {
12219
+ if (el !== tabbar) el.remove();
12220
+ });
12221
+ const on = narrow() && !!frameEl() && !dialogOpen();
12222
+ const collapsed = sidebarCollapsed();
12223
+ const right = rightOpen();
12224
+ if (scrim) scrim.style.display = on && !collapsed ? "" : "none";
12225
+ tabbar.style.display = narrow() && !!frameEl() && !panelNav.open && !right ? "" : "none";
12226
+ syncTabs();
12227
+ };
12228
+ sync();
12229
+ pollTasks();
12230
+ const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(sync) : null;
12231
+ if (observer) observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-sidebar-collapsed"], subtree: true });
12232
+ const timer = setInterval(sync, 1500);
12233
+ const onMq = () => sync();
12234
+ if (mq.addEventListener) mq.addEventListener("change", onMq);
12235
+ else if (mq.addListener) mq.addListener(onMq);
12236
+ window.addEventListener("resize", kbdSync);
12237
+ if (vv && vv.addEventListener) vv.addEventListener("resize", kbdSync);
12238
+ const onVisible = () => {
12239
+ if (document.visibilityState === "visible") pollTasks();
12240
+ };
12241
+ document.addEventListener("visibilitychange", onVisible);
12242
+ const onClick = (e) => {
12243
+ if (!narrow()) return;
12244
+ const t = e.target;
12245
+ if (!t || !t.closest) return;
12246
+ if (t.closest(".dsh-mobile-tabbar")) return;
12247
+ const expanded = !!document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
12248
+ const col = document.querySelector('[class*="sidebarCol"]');
12249
+ const inSidebar = col && col.contains(t);
12250
+ if (!expanded && !inSidebar) return;
12251
+ if (inSidebar) {
12252
+ const row = t.closest('[role="treeitem"]');
12253
+ const rowKey = row && row.getAttribute("data-row-key") || "";
12254
+ const sessionRow = rowKey && rowKey.startsWith("session:") || row && !rowKey && !row.querySelector('[role="treeitem"]') && row.closest('[role="tree"]');
12255
+ const fresh = t.closest('[class*="newSession"]');
12256
+ if (sessionRow || fresh) {
12257
+ setTimeout(() => {
12258
+ collapse();
12259
+ closeDetails();
12260
+ }, 300);
12261
+ return;
12262
+ }
12263
+ if (rowKey.startsWith("workspace:")) return;
12264
+ setTimeout(() => {
12265
+ if (document.querySelector('[role="dialog"]')) collapse();
12266
+ }, 260);
12267
+ return;
11667
12268
  }
12269
+ if (t.closest('[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[data-sidebar-right-panel]')) return;
12270
+ collapse();
12271
+ };
12272
+ document.addEventListener("click", onClick, true);
12273
+ return () => {
12274
+ document.removeEventListener("click", onClick, true);
12275
+ if (observer) observer.disconnect();
12276
+ clearInterval(timer);
12277
+ clearInterval(tasksTimer);
12278
+ unsubPanel();
12279
+ if (mq.removeEventListener) mq.removeEventListener("change", onMq);
12280
+ else if (mq.removeListener) mq.removeListener(onMq);
12281
+ window.removeEventListener("resize", kbdSync);
12282
+ if (vv && vv.removeEventListener) vv.removeEventListener("resize", kbdSync);
12283
+ document.removeEventListener("visibilitychange", onVisible);
12284
+ document.documentElement.classList.remove("dsh-dock-kbd");
12285
+ if (scrim) scrim.remove();
12286
+ tabbar.remove();
12287
+ if (window.__dshDockMobileDrawer === "client") delete window.__dshDockMobileDrawer;
11668
12288
  };
11669
- return import_react22.default.createElement("button", {
11670
- type: "button",
11671
- className: "dkmob-fab",
11672
- "aria-label": "\u5F00\u5173\u4FA7\u680F",
11673
- title: "\u5F00\u5173\u4FA7\u680F",
11674
- onClick: onToggle
11675
- }, import_react22.default.createElement(
11676
- "svg",
11677
- { width: 20, height: 20, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.3, "aria-hidden": true },
11678
- import_react22.default.createElement("rect", { x: 1.5, y: 2.5, width: 13, height: 11, rx: 2 }),
11679
- import_react22.default.createElement("line", { x1: 6, y1: 2.5, x2: 6, y2: 13.5 })
11680
- ));
11681
12289
  }
11682
12290
  function useViewportSize() {
11683
12291
  const [size, setSize] = (0, import_react23.useState)(() => typeof window === "undefined" ? null : { w: window.innerWidth, h: window.innerHeight });
@@ -11689,31 +12297,38 @@ function useViewportSize() {
11689
12297
  }, []);
11690
12298
  return size;
11691
12299
  }
11692
- function MobileView() {
12300
+ function useMobileState() {
11693
12301
  const [active, setActive] = (0, import_react23.useState)(() => mobileActive());
11694
- const [floating, setFloating] = (0, import_react23.useState)(() => floatingShown());
11695
- const size = useViewportSize();
12302
+ const [chrome, setChrome] = (0, import_react23.useState)(() => typeof window === "undefined" || typeof window.matchMedia !== "function" ? false : window.matchMedia(MOBILE_QUERY_CHROME).matches);
12303
+ const [owner, setOwner] = (0, import_react23.useState)(() => typeof window === "undefined" ? null : window.__dshDockMobileDrawer || null);
11696
12304
  (0, import_react23.useEffect)(() => {
11697
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
12305
+ const offChrome = watchQuery(MOBILE_QUERY_CHROME, setChrome);
11698
12306
  let mqs = [];
11699
12307
  try {
11700
12308
  mqs = [window.matchMedia(MOBILE_QUERY_NARROW), window.matchMedia(MOBILE_QUERY_COARSE)];
11701
12309
  } catch {
11702
- return void 0;
12310
+ mqs = [];
11703
12311
  }
11704
- const update = () => setActive(mqs.some((mq) => mq.matches));
11705
- update();
12312
+ const compute = () => setActive(mqs.some((mq) => mq.matches));
11706
12313
  for (const mq of mqs) {
11707
- if (typeof mq.addEventListener === "function") mq.addEventListener("change", update);
11708
- else if (typeof mq.addListener === "function") mq.addListener(update);
12314
+ if (typeof mq.addEventListener === "function") mq.addEventListener("change", compute);
12315
+ else if (typeof mq.addListener === "function") mq.addListener(compute);
11709
12316
  }
12317
+ const timer = setInterval(() => setOwner(typeof window === "undefined" ? null : window.__dshDockMobileDrawer || null), 1e3);
11710
12318
  return () => {
12319
+ offChrome();
11711
12320
  for (const mq of mqs) {
11712
- if (typeof mq.removeEventListener === "function") mq.removeEventListener("change", update);
11713
- else if (typeof mq.removeListener === "function") mq.removeListener(update);
12321
+ if (typeof mq.removeEventListener === "function") mq.removeEventListener("change", compute);
12322
+ else if (typeof mq.removeListener === "function") mq.removeListener(compute);
11714
12323
  }
12324
+ clearInterval(timer);
11715
12325
  };
11716
12326
  }, []);
12327
+ return { active, chrome, owner };
12328
+ }
12329
+ function MobileView() {
12330
+ const { active, chrome, owner } = useMobileState();
12331
+ const size = useViewportSize();
11717
12332
  return import_react22.default.createElement(
11718
12333
  "div",
11719
12334
  { className: "dkmob-root" },
@@ -11732,73 +12347,44 @@ function MobileView() {
11732
12347
  import_react22.default.createElement(
11733
12348
  "div",
11734
12349
  { className: "dkmob-row" },
11735
- import_react22.default.createElement("span", { className: "dkmob-label" }, "\u6D6E\u52A8\u4FA7\u680F\u6309\u94AE"),
11736
- import_react22.default.createElement("button", {
11737
- type: "button",
11738
- className: "dock-sw" + (floating ? " on" : ""),
11739
- role: "switch",
11740
- "aria-checked": floating,
11741
- "aria-label": (floating ? "\u9690\u85CF" : "\u663E\u793A") + "\u6D6E\u52A8\u4FA7\u680F\u6309\u94AE",
11742
- title: floating ? "\u9690\u85CF\u5DE6\u4E0B\u89D2\u7684\u6D6E\u52A8\u4FA7\u680F\u5F00\u5173" : "\u663E\u793A\u5DE6\u4E0B\u89D2\u7684\u6D6E\u52A8\u4FA7\u680F\u5F00\u5173",
11743
- onClick: () => setFloatingShown(!floating)
11744
- }),
11745
- import_react22.default.createElement("span", { className: "dkmob-dim" }, "44px \u5706\u94AE\u56FA\u5B9A\u5728\u5DE6\u4E0B\u89D2\uFF0C\u8C03\u7528 dsh \u5B98\u65B9 toggleSidebar \u5F00\u5408\u7A84\u5C4F\u62BD\u5C49\uFF1B\u4E0D\u9700\u8981\u65F6\u53EF\u5728\u6B64\u5173\u95ED\u3002")
12350
+ import_react22.default.createElement("span", { className: "dkmob-label" }, "\u624B\u673A\u7AEF chrome"),
12351
+ import_react22.default.createElement("span", { className: "dkmob-state" + (chrome ? " on" : "") }, chrome ? "\u2264700px\uFF0C\u751F\u6548\u4E2D" : "\u672A\u547D\u4E2D"),
12352
+ import_react22.default.createElement(
12353
+ "span",
12354
+ { className: "dkmob-dim" },
12355
+ "\u5E95\u90E8 Tab \u680F\uFF08\u4F1A\u8BDD / \u63D2\u4EF6 / \u8BBE\u7F6E\uFF09+ \u4FA7\u680F\u62BD\u5C49\u906E\u7F69 + \u8F6F\u952E\u76D8\u8BA9\u4F4D\uFF1B\u4F1A\u8BDD\u5165\u53E3\u5728\u5E95\u90E8\u300C\u4F1A\u8BDD\u300D\u9875\u7B7E\uFF0C\u4E0D\u8BBE\u5DE6\u7F18\u6D6E\u52A8\u6309\u94AE\u3002",
12356
+ owner === "host" ? "\u5F53\u524D\u7531\u5BBF\u4E3B\u6CE8\u5165\u7248\u9A71\u52A8\uFF08\u91CD\u542F dsh web \u540E\u751F\u6548\u7684\u90A3\u5957\uFF09\u3002" : owner === "client" ? "\u5F53\u524D\u7531\u5BA2\u6237\u7AEF\u81EA\u5EFA\u7248\u9A71\u52A8\uFF08\u5237\u65B0\u5373\u751F\u6548\uFF09\u3002" : ""
12357
+ )
11746
12358
  ),
11747
12359
  import_react22.default.createElement(
11748
12360
  "div",
11749
12361
  { className: "dkmob-note" },
11750
12362
  "\u751F\u6548\u4E2D\u7684\u4F18\u5316\uFF1A\u8F93\u5165\u7C7B\u63A7\u4EF6\u5B57\u53F7\u62C9\u5230 16px\uFF08\u907F\u514D iOS \u805A\u7126\u7F29\u653E\u6574\u9875\uFF09\xB7 \u7EAF\u56FE\u6807\u6309\u94AE\u9762\u79EF\u515C\u5E95 \u226536px \xB7 ",
11751
- "\u53BB\u6389\u70B9\u6309\u9AD8\u4EAE\u4E0E\u53CC\u51FB\u7F29\u653E\u5224\u5B9A \xB7 \u4EE3\u7801\u5757\u6A2A\u5411\u6EDA\u52A8\u3001\u884C\u5185\u4EE3\u7801\u81EA\u52A8\u6362\u884C \xB7 viewport meta \u8865 viewport-fit=cover",
12363
+ "\u53BB\u6389\u70B9\u6309\u9AD8\u4EAE\u4E0E\u53CC\u51FB\u7F29\u653E\u5224\u5B9A \xB7 \u4EE3\u7801\u5757\u6A2A\u5411\u6EDA\u52A8\u3001\u884C\u5185\u4EE3\u7801\u81EA\u52A8\u6362\u884C \xB7 \u6EDA\u52A8\u4F53 touch-action:pan-y + iOS \u60EF\u6027\u6EDA\u52A8 \xB7 ",
12364
+ "\u8BBE\u7F6E\u5F39\u5C42\u5185\u5BB9\u5217 min-height:0\uFF08\u5426\u5219\u7A84\u5C4F\u5F39\u5C42\u88AB\u5185\u5BB9\u6491\u9AD8\u540E\u6574\u7247\u6ED1\u4E0D\u52A8\uFF09\xB7 viewport meta \u8865 viewport-fit=cover",
11752
12365
  "\uFF08standalone \u5168\u5C4F\u65F6\u7ED9\u5706\u89D2/\u5218\u6D77\u7559\u5B89\u5168\u533A\uFF09\u3002\u505C\u7528\u672C\u529F\u80FD\u5373\u5168\u90E8\u8FD8\u539F\u3002"
11753
12366
  )
11754
12367
  );
11755
12368
  }
11756
12369
  function MobileStat() {
11757
- const [active, setActive] = (0, import_react23.useState)(() => mobileActive());
11758
- const [floating, setFloating] = (0, import_react23.useState)(() => floatingShown());
11759
- (0, import_react23.useEffect)(() => {
11760
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
11761
- let mqs = [];
11762
- try {
11763
- mqs = [window.matchMedia(MOBILE_QUERY_NARROW), window.matchMedia(MOBILE_QUERY_COARSE)];
11764
- } catch {
11765
- return void 0;
11766
- }
11767
- const update = () => setActive(mqs.some((mq) => mq.matches));
11768
- update();
11769
- for (const mq of mqs) {
11770
- if (typeof mq.addEventListener === "function") mq.addEventListener("change", update);
11771
- else if (typeof mq.addListener === "function") mq.addListener(update);
11772
- }
11773
- return () => {
11774
- for (const mq of mqs) {
11775
- if (typeof mq.removeEventListener === "function") mq.removeEventListener("change", update);
11776
- else if (typeof mq.removeListener === "function") mq.removeListener(update);
11777
- }
11778
- };
11779
- }, []);
11780
- (0, import_react23.useEffect)(() => subscribePref(() => setFloating(floatingShown())), []);
12370
+ const { active, chrome } = useMobileState();
11781
12371
  if (!active) return import_react22.default.createElement("span", null, "\u5F53\u524D\u89C6\u53E3\u672A\u547D\u4E2D\u624B\u673A\u5F62\u6001\uFF0C\u6837\u5F0F\u672A\u6CE8\u5165");
11782
- return import_react22.default.createElement("span", null, "\u624B\u673A\u6A21\u5F0F\u751F\u6548\u4E2D \xB7 \u6D6E\u52A8\u4FA7\u680F\u6309\u94AE" + (floating ? "\u663E\u793A" : "\u5DF2\u9690\u85CF"));
12372
+ return import_react22.default.createElement("span", null, "\u624B\u673A\u6A21\u5F0F\u751F\u6548\u4E2D \xB7 " + (chrome ? "\u5E95\u90E8 Tab \u680F\u5DF2\u63A5\u7BA1\u4F1A\u8BDD\u5165\u53E3" : "chrome \u5E26\u5BBD\u672A\u547D\u4E2D\uFF08\u2264700px \u624D\u542F\u7528\uFF09"));
11783
12373
  }
11784
12374
  var css6 = [
11785
- // 浮动侧栏按钮:44px 触控面积(Apple HIG 最低建议),token 取主题变量自动适配亮暗色;
11786
- // z-index 190 压在功能坞弹层(dockm-backdrop,200)之下,弹层打开时被遮罩盖住不误触。
11787
- ".dkmob-fab{position:fixed;left:calc(env(safe-area-inset-left,0px) + 62px);bottom:calc(env(safe-area-inset-bottom,0px) + 74px);z-index:190;width:44px;height:44px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;padding:0;box-shadow:0 4px 14px rgb(0 0 0 / .22);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;transition:transform .12s var(--ds-ease-in-out),background .15s var(--ds-ease-in-out),color .15s var(--ds-ease-in-out);}",
11788
- ".dkmob-fab:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
11789
- ".dkmob-fab:active{transform:scale(.94);}",
11790
12375
  // 输入类控件 16px:防 iOS 聚焦自动放大(<16px 的输入框聚焦时 Safari 强制缩放页面)
11791
12376
  'body[data-dk-mobile="1"] :is(textarea, input:not([type]), input[type="text"], input[type="search"], input[type="url"], input[type="password"], input[type="number"], input[type="email"], [contenteditable="true"]){font-size:16px !important;}',
11792
12377
  // 触控基础:去点按高亮、消双击缩放判定
11793
12378
  'body[data-dk-mobile="1"] :is(button, [role="button"]){-webkit-tap-highlight-color:transparent;touch-action:manipulation;}',
11794
12379
  // 纯图标按钮面积兜底:实测窄屏下一批 28×28 的工具钮很难点中;36px 是不引起行高剧变的上限。
11795
- // 只收「唯一子元素是 svg」的按钮,避免撑坏带文字的行内控件;排除远程访问的抽屉把手
11796
- // (.dsh-mobile-drawer-btn 贴边设计 30px 宽,被撑到 36 会破坏边缘吸附观感)与本模块 FAB。
11797
- 'body[data-dk-mobile="1"] button:has(> svg:only-child):not(.dsh-mobile-drawer-btn):not(.dkmob-fab){min-width:36px;min-height:36px;}',
12380
+ // 只收「唯一子元素是 svg」的按钮,避免撑坏带文字的行内控件;排除本模块 chrome 按钮。
12381
+ 'body[data-dk-mobile="1"] button:has(> svg:only-child):not(.dsh-mobile-tab){min-width:36px;min-height:36px;}',
11798
12382
  // 代码块横向滚动、行内代码换行:窄屏不撑破版面
11799
12383
  'body[data-dk-mobile="1"] pre{max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;}',
11800
12384
  'body[data-dk-mobile="1"] code{overflow-wrap:anywhere;}',
11801
12385
  'body[data-dk-mobile="1"] pre code{overflow-wrap:normal;}',
12386
+ // 滚动兜底:所有纵向滚动体可纵向平移 + iOS 惯性滚动(祖先合成层/触控声明不再吃掉触摸滚动)。
12387
+ 'body[data-dk-mobile="1"] :is([class*="scrollBody"], [class*="sidebarCol"] [class*="list"], [role="dialog"] [class*="options"], [class*="dockm-content"]){-webkit-overflow-scrolling:touch;overscroll-behavior-y:contain;touch-action:pan-y;}',
11802
12388
  // iOS 横竖屏切换时的字号自动膨胀关掉(:has 挂到 html 上;dsh 自身样式已依赖 :has,基线足够)
11803
12389
  'html:has(body[data-dk-mobile="1"]){-webkit-text-size-adjust:100%;text-size-adjust:100%;}',
11804
12390
  // 功能页自身
@@ -11808,14 +12394,66 @@ var css6 = [
11808
12394
  ".dkmob-state{flex:none;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 10px;font-size:11px;}",
11809
12395
  ".dkmob-state.on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);}",
11810
12396
  ".dkmob-dim{color:var(--dsw-alias-label-secondary);font-size:12px;}",
11811
- ".dkmob-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}"
12397
+ ".dkmob-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
12398
+ // 手机端 chrome:底部 Tab 栏 + 抽屉遮罩(窄屏 ≤700px;与宿主半部同款)
12399
+ "@media (max-width:700px){",
12400
+ ".dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}",
12401
+ ".dsh-mobile-tabbar{position:fixed;left:0;right:0;bottom:0;z-index:65;display:flex;align-items:stretch;justify-content:space-around;padding:6px 8px calc(8px + env(safe-area-inset-bottom,0px));background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 92%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.25));touch-action:manipulation}",
12402
+ ".dsh-mobile-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:48px;padding:4px 0;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#8b95ab);font:600 11px/1.2 inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:10px;position:relative}",
12403
+ ".dsh-mobile-tab svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}",
12404
+ ".dsh-mobile-tab.on{color:var(--dsw-alias-label-primary,#e6eaf2)}",
12405
+ ".dsh-mobile-tab.on svg{color:var(--dk-accent,#4d9fff)}",
12406
+ ".dsh-mobile-tab:active{transform:scale(.94)}",
12407
+ ".dsh-mobile-tab-badge{position:absolute;top:0;right:calc(50% - 26px);box-sizing:border-box;min-width:16px;height:16px;padding:0 4px;border-radius:9px;display:flex;align-items:center;justify-content:center;background:#4d9fff;color:#fff;font-size:10px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;box-shadow:0 2px 8px rgba(0,0,0,.35);pointer-events:none}",
12408
+ ".dsh-mobile-tab-badge.wait{background:#ef4444;animation:dsh-badge-pulse 1.2s ease-in-out infinite}",
12409
+ "@keyframes dsh-badge-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}",
12410
+ "@media (prefers-reduced-motion:reduce){.dsh-mobile-tab-badge.wait{animation:none}}",
12411
+ // ⚠️ 「设置页滑不动」的关键兜底(客户端无条件携带 = 刷新即生效,不必等宿主重启):
12412
+ // 窄屏把设置弹层改成纵向堆叠后,内容列若是默认 min-height:auto 就会被内容撑高
12413
+ // (无头 390×844 实测列高 1216px 塞在 844px 视口里)、再被 .panel 的 overflow:hidden
12414
+ // 裁掉——列内滚动容器([class*=options])因此 scrollHeight==clientHeight,手指怎么滑
12415
+ // 都不动。补 min-height:0 后 scrollTop 0 → 425。宿主半部 MOBILE_LAYOUT_CSS 同款;
12416
+ // 两处都写是因为宿主注入要重启 dsh web 才更新,而客户端样式刷新即生效,覆盖窗口期。
12417
+ '[role="dialog"][class*="panel"]>[class*="content"]{min-height:0!important}',
12418
+ // 输入区吸附到 Tab 栏上方 + 滚动体留白(与宿主半部同款)。
12419
+ // ⚠️ 绝不能给滚动体加 justify-content:flex-end/center 贴底:内容会被挤到滚动体
12420
+ // 起点之外,Chrome 不把起点方向溢出算进 scrollHeight(手机视口实测
12421
+ // scrollHeight==clientHeight、maxScroll==0),会话记录彻底滚不动、首条消息永远看不到。
12422
+ // 宿主半部旧版注入里还留着那条 flex-end(要重启 dsh web 才消失),这里用更高优先级
12423
+ // 的选择器显式复位:刷新页面即修好,不必等重启。
12424
+ '[class*="scrollBody"][data-conversation-scroll]{justify-content:flex-start !important;padding-bottom:calc(72px + env(safe-area-inset-bottom,0px)) !important}',
12425
+ 'html:not(.dsh-dock-kbd) [class*="scrollBody"]{padding-bottom:calc(72px + env(safe-area-inset-bottom,0px)) !important}',
12426
+ 'html.dsh-dock-kbd [class*="scrollBody"]{padding-bottom:12px !important}',
12427
+ '[class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:6px}',
12428
+ // 输入区工具行上的 dsh-dock chip(用量/余额):窄屏下 28cqw 的宽度上限把它们挤成
12429
+ // 「…」「余..」(手机实测),既读不出数值又白占输入行空间。手机端整组收起,数值在
12430
+ // 底部「插件」里看(用量/余额页信息更全);桌面端不动。
12431
+ ".dockchip-row{display:none}",
12432
+ '[class*="sidebarCol"]:has([class*="collapsed"]){visibility:hidden}',
12433
+ // ⚠️ 列级 visibility:hidden 在 dsh 0.2.0 上挡不住:工作区浏览器给自己的
12434
+ // headerActions/sectionLabel 显式声明 visibility:visible(展开/收拢动画用),visibility
12435
+ // 可被后代覆盖,轨道按钮会从隐藏列里「透」出来(实测手机截图左缘游离按钮)。
12436
+ // display:none 不可被后代翻转——但只能打在**轨道根节点**上(类名同时含 root 与
12437
+ // collapsed 两个语义后缀),绝不能打在列上:frame 是「侧栏 0px + 中列 1fr + 右轨 0px」
12438
+ // 的网格,列 display:none 会从网格流里摘掉,中列被自动放置顶进 0px 轨(实测整页空屏)。
12439
+ // 弹层打开时豁免(设置弹层挂在列内部,display:none 会连坐成空气弹层),此时列退回
12440
+ // visibility 机制(由下一条 [role=dialog] 强制可见),窄屏弹层全屏铺满盖住透出的轨道。
12441
+ '[class*="sidebarCol"]:has([class*="collapsed"]):not(:has([role="dialog"])) [class*="root"][class*="collapsed"]{display:none !important}',
12442
+ // 设置/插件管理弹层挂在侧栏列内部:上面隐藏整列会把它连坐隐藏(空气弹层)。
12443
+ // visibility 可被后代覆盖,弹层关闭态整体卸载,所以只在真正打开时命中。
12444
+ '[class*="sidebarCol"]:has([class*="collapsed"]) [role="dialog"]{visibility:visible}',
12445
+ "html.dsh-dock-kbd .dsh-mobile-tabbar{display:none !important}",
12446
+ // 功能坞面板在手机端抬到原生弹层之上:从侧栏进的官方插件管理页等原生弹层 z 序高于
12447
+ // 面板默认的 200,功能坞开在下面会被盖住——「插件」页签点了像没反应/露出原页面。
12448
+ '[class*="dockm-backdrop"]{z-index:2000 !important}',
12449
+ "}"
11812
12450
  ].join("\n");
11813
12451
  var feature11 = {
11814
12452
  id: "mobile",
11815
12453
  name: "\u624B\u673A\u9002\u914D",
11816
12454
  order: 170,
11817
12455
  accent: "#34d399",
11818
- description: "\u624B\u673A/\u7A84\u5C4F\u6392\u7248\u4E0E\u89E6\u63A7\u515C\u5E95\uFF1A16px \u8F93\u5165\u9632 iOS \u7F29\u653E\u3001\u56FE\u6807\u6309\u94AE\u89E6\u63A7\u9762\u79EF\u3001\u4EE3\u7801\u5757\u6EDA\u52A8\u3001\u6D6E\u52A8\u4FA7\u680F\u5F00\u5173",
12456
+ description: "\u624B\u673A/\u7A84\u5C4F\u6392\u7248\u4E0E\u89E6\u63A7\u515C\u5E95\uFF1A\u5E95\u90E8 Tab \u680F + \u4FA7\u680F\u62BD\u5C49\u300116px \u8F93\u5165\u9632 iOS \u7F29\u653E\u3001\u56FE\u6807\u6309\u94AE\u89E6\u63A7\u9762\u79EF\u3001\u4EE3\u7801\u5757\u6EDA\u52A8\u3001\u6EDA\u52A8\u515C\u5E95",
11819
12457
  defaultEnabled: true,
11820
12458
  css: css6,
11821
12459
  Overlay: MobileOverlay,
@@ -11824,7 +12462,7 @@ var feature11 = {
11824
12462
  };
11825
12463
 
11826
12464
  // src/client.jsx
11827
- var DOCK_VERSION = "0.13.0";
12465
+ var DOCK_VERSION = "0.14.0";
11828
12466
  var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8, feature9, feature10, feature11];
11829
12467
  var PLANNED_FEATURES = [];
11830
12468
  var PLANNED_NOTES = {};