@wenbin_wb/dsh-bridge 2.2.5 → 2.2.6

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/client.js CHANGED
@@ -42,6 +42,7 @@ var BRIDGE_ENDPOINTS = {
42
42
  resetCloudflared: "resetCloudflared",
43
43
  saveCustomTunnelConfig: "saveCustomTunnelConfig",
44
44
  checkVersion: "checkVersion",
45
+ upgradePlugin: "upgradePlugin",
45
46
  // 平台管理器(多 IM 平台统一接口)
46
47
  listPlatforms: "listPlatforms",
47
48
  platformLogin: "platformLogin",
@@ -103,6 +104,59 @@ var s = {
103
104
  warn: { background: "var(--dsw-alias-state-warn-bg,#fffbeb)", border: "1px solid var(--dsw-alias-state-warn-border,#fde68a)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-state-warn-primary,#92400e)", lineHeight: 1.6 },
104
105
  tip: { background: "var(--dsw-alias-bg-layer-2,#f9fafb)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.6 }
105
106
  };
107
+ var Icons = {
108
+ wechat: (props) => React.createElement(
109
+ "svg",
110
+ { viewBox: "0 0 24 24", width: 18, height: 18, fill: "currentColor", ...props },
111
+ React.createElement("path", { d: "M8.5 2C4.36 2 1 4.91 1 8.5c0 2.01 1.05 3.81 2.69 4.97l-.69 2.06 2.45-1.22c.94.43 1.98.69 3.05.69.21 0 .42-.01.62-.03-.23-.62-.37-1.28-.37-1.97 0-3.59 3.36-6.5 7.5-6.5.21 0 .41.01.62.03C15.87 4.54 12.44 2 8.5 2zM6 6.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm5 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm7.5 3.5c-3.59 0-6.5 2.46-6.5 5.5 0 1.66.86 3.14 2.21 4.1l-.56 1.69 2.01-1c.78.36 1.64.57 2.54.57 3.59 0 6.5-2.46 6.5-5.5s-2.91-5.5-6.5-5.5zm-2 4a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" })
112
+ ),
113
+ qq: (props) => React.createElement(
114
+ "svg",
115
+ { viewBox: "0 0 24 24", width: 18, height: 18, fill: "currentColor", ...props },
116
+ React.createElement("path", { d: "M12 2C7.58 2 4 5.37 4 9.53c0 1.95.78 3.73 2.07 5.07-.37 1.15-.99 2.19-1.8 3.08-.18.2-.04.52.23.52 2.22 0 3.99-1.07 4.9-1.86.8.25 1.66.39 2.6.39 4.42 0 8-3.37 8-7.53S16.42 2 12 2zm-3 8a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm6 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z" })
117
+ ),
118
+ feishu: (props) => React.createElement(
119
+ "svg",
120
+ { viewBox: "0 0 24 24", width: 18, height: 18, fill: "currentColor", ...props },
121
+ React.createElement("path", { d: "M12 2.5L3.5 11.2l6.8 1.8 2.2 6.5 1.8-4.7 5.2-1.4L12 2.5zm-.8 11.1l-4.1-1.1 6.5-6.6-4.2 8.3 1.8-.6z" })
122
+ ),
123
+ telegram: (props) => React.createElement(
124
+ "svg",
125
+ { viewBox: "0 0 24 24", width: 18, height: 18, fill: "currentColor", ...props },
126
+ React.createElement("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 0 0-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z" })
127
+ ),
128
+ lan: (props) => React.createElement(
129
+ "svg",
130
+ { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
131
+ React.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9 0 2.12.74 4.07 1.97 5.61L4.29 19.3a1 1 0 0 0 1.41 1.41l1.7-1.7C9.02 19.64 10.46 20 12 20c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 15c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z" })
132
+ ),
133
+ tunnel: (props) => React.createElement(
134
+ "svg",
135
+ { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
136
+ React.createElement("path", { d: "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z" })
137
+ ),
138
+ bot: (props) => React.createElement(
139
+ "svg",
140
+ { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
141
+ React.createElement("path", { d: "M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zm-9-6c-.83 0-1.5-.67-1.5-1.5S8.17 10 9 10s1.5.67 1.5 1.5S9.83 13 9 13zm6 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" })
142
+ ),
143
+ github: (props) => React.createElement(
144
+ "svg",
145
+ { viewBox: "0 0 24 24", width: 13, height: 13, fill: "currentColor", ...props },
146
+ React.createElement("path", { d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" })
147
+ ),
148
+ refresh: (props) => React.createElement(
149
+ "svg",
150
+ { viewBox: "0 0 24 24", width: 12, height: 12, fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", ...props },
151
+ React.createElement("path", { d: "M23 4v6h-6M1 20v-6h6" }),
152
+ React.createElement("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
153
+ ),
154
+ check: (props) => React.createElement(
155
+ "svg",
156
+ { viewBox: "0 0 24 24", width: 12, height: 12, fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", ...props },
157
+ React.createElement("polyline", { points: "20 6 9 17 4 12" })
158
+ )
159
+ };
106
160
  function useCopy() {
107
161
  const [copied, setCopied] = React.useState(false);
108
162
  const copy = React.useCallback((text) => {
@@ -472,7 +526,15 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
472
526
  React.createElement(
473
527
  "div",
474
528
  null,
475
- React.createElement("div", { style: s.label }, platformName),
529
+ React.createElement(
530
+ "div",
531
+ { style: { ...s.label, display: "flex", alignItems: "center", gap: 7 } },
532
+ platformId === "wechat" && React.createElement(Icons.wechat, { style: { color: "#07C160", width: 20, height: 20 } }),
533
+ platformId === "qq" && React.createElement(Icons.qq, { style: { color: "#12B7F5", width: 20, height: 20 } }),
534
+ platformId === "feishu" && React.createElement(Icons.feishu, { style: { color: "#00D6B9", width: 20, height: 20 } }),
535
+ platformId === "telegram" && React.createElement(Icons.telegram, { style: { color: "#24A1DE", width: 20, height: 20 } }),
536
+ platformName
537
+ ),
476
538
  React.createElement("div", { style: { ...s.muted, marginTop: 2 } }, platformDesc)
477
539
  ),
478
540
  React.createElement(StatusTag, { status: platform?.status, running: connected })
@@ -887,7 +949,11 @@ function UpgradeCommandRow({ cmd }) {
887
949
  color: "var(--dsw-alias-label-secondary,#6b7280)",
888
950
  flex: 1,
889
951
  minWidth: 0,
890
- wordBreak: "break-all"
952
+ wordBreak: "break-all",
953
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
954
+ padding: "4px 8px",
955
+ borderRadius: 6,
956
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
891
957
  }
892
958
  }, cmd),
893
959
  React.createElement("button", {
@@ -900,6 +966,9 @@ function UpgradeCommandRow({ cmd }) {
900
966
  function VersionBanner({ rpcCall }) {
901
967
  const [info, setInfo] = React.useState(null);
902
968
  const [loading, setLoading] = React.useState(false);
969
+ const [upgrading, setUpgrading] = React.useState(false);
970
+ const [upgradeResult, setUpgradeResult] = React.useState(null);
971
+ const [showManual, setShowManual] = React.useState(false);
903
972
  const check = React.useCallback(async () => {
904
973
  setLoading(true);
905
974
  try {
@@ -913,6 +982,27 @@ function VersionBanner({ rpcCall }) {
913
982
  check();
914
983
  }, [check]);
915
984
  const hasUpdate = info?.latest && info?.current && !info.error && semverGt(info.latest, info.current);
985
+ const isLatest = info?.latest && info?.current && !info.error && !semverGt(info.latest, info.current);
986
+ const handleUpgrade = React.useCallback(async () => {
987
+ if (!info?.latest || upgrading) return;
988
+ setUpgrading(true);
989
+ setUpgradeResult(null);
990
+ try {
991
+ const r = await rpcCall(BRIDGE_ENDPOINTS.upgradePlugin, { version: info.latest });
992
+ if (r?.ok && r.value?.ok) {
993
+ setUpgradeResult({ ok: true, message: `\u5DF2\u6210\u529F\u5347\u7EA7\u5230 v${info.latest}\uFF01\u8BF7\u91CD\u542F DSH \u670D\u52A1\u4F7F\u65B0\u7248\u672C\u751F\u6548\u3002` });
994
+ setTimeout(() => check(), 3e3);
995
+ } else {
996
+ setUpgradeResult({ ok: false, message: r?.value?.error || r?.error?.message || "\u5347\u7EA7\u5931\u8D25" });
997
+ setShowManual(true);
998
+ }
999
+ } catch (e) {
1000
+ setUpgradeResult({ ok: false, message: e.message || "\u5347\u7EA7\u8BF7\u6C42\u5931\u8D25" });
1001
+ setShowManual(true);
1002
+ } finally {
1003
+ setUpgrading(false);
1004
+ }
1005
+ }, [info?.latest, upgrading, rpcCall, check]);
916
1006
  const links = React.createElement(
917
1007
  "div",
918
1008
  { style: { display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" } },
@@ -921,95 +1011,174 @@ function VersionBanner({ rpcCall }) {
921
1011
  target: "_blank",
922
1012
  rel: "noreferrer",
923
1013
  style: s.btnLink
924
- }, "\u2B50 GitHub"),
1014
+ }, React.createElement(Icons.github), "GitHub"),
925
1015
  React.createElement("a", {
926
1016
  href: RELEASES_URL,
927
1017
  target: "_blank",
928
1018
  rel: "noreferrer",
929
1019
  style: s.btnLink
930
- }, "\u{1F4CB} \u66F4\u65B0\u65E5\u5FD7"),
1020
+ }, "\u66F4\u65B0\u65E5\u5FD7"),
931
1021
  React.createElement("a", {
932
1022
  href: ISSUES_URL,
933
1023
  target: "_blank",
934
1024
  rel: "noreferrer",
935
1025
  style: s.btnLink
936
- }, "\u{1F41B} \u53CD\u9988 Bug")
1026
+ }, "\u53CD\u9988 Issue")
937
1027
  );
938
- if (hasUpdate) {
939
- const cmds = upgradeCommands(info.latest);
940
- return React.createElement(
1028
+ return React.createElement(
1029
+ "div",
1030
+ { style: { marginBottom: 16 } },
1031
+ // 顶部状态行:版本徽标 + 刷新按钮 + 链接组
1032
+ React.createElement(
941
1033
  "div",
942
- { style: { marginBottom: 16 } },
1034
+ {
1035
+ style: {
1036
+ display: "flex",
1037
+ alignItems: "center",
1038
+ justifyContent: "space-between",
1039
+ flexWrap: "wrap",
1040
+ gap: 8
1041
+ }
1042
+ },
943
1043
  React.createElement(
944
1044
  "div",
945
- {
946
- style: {
947
- ...s.card,
948
- background: "var(--dsw-alias-state-info-bg,#eff6ff)",
949
- border: "1px solid var(--dsw-alias-state-info-border,#bfdbfe)",
950
- padding: "12px 16px",
951
- marginBottom: 8
952
- }
953
- },
1045
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
1046
+ // 版本状态徽标
1047
+ React.createElement(
1048
+ "span",
1049
+ {
1050
+ style: {
1051
+ ...s.tag,
1052
+ background: hasUpdate ? "var(--dsw-alias-state-info-bg,#eff6ff)" : isLatest ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-bg-layer-2,#f3f4f6)",
1053
+ color: hasUpdate ? "var(--dsw-alias-state-info-primary,#2563eb)" : isLatest ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-label-secondary,#6b7280)",
1054
+ padding: "3px 10px",
1055
+ fontSize: 12,
1056
+ fontWeight: 500,
1057
+ display: "inline-flex",
1058
+ alignItems: "center",
1059
+ gap: 5
1060
+ }
1061
+ },
1062
+ loading ? React.createElement("span", {
1063
+ style: { display: "inline-flex", alignItems: "center" }
1064
+ }, React.createElement(Icons.refresh)) : isLatest ? React.createElement(Icons.check) : null,
1065
+ info ? `v${info.current}` : "\u7248\u672C\u68C0\u67E5\u4E2D\u2026",
1066
+ isLatest && React.createElement("span", { style: { opacity: 0.85, fontSize: 11, fontWeight: 400 } }, "\xB7 \u5DF2\u662F\u6700\u65B0"),
1067
+ hasUpdate && React.createElement("span", { style: { fontWeight: 600, fontSize: 11 } }, `\u2794 v${info.latest}`),
1068
+ info?.error && React.createElement("span", { style: { color: "var(--dsw-alias-state-warn-primary,#d97706)", fontSize: 11 } }, "(\u7F51\u7EDC\u8D85\u65F6)")
1069
+ ),
1070
+ // 刷新检查按钮
1071
+ React.createElement(
1072
+ "button",
1073
+ {
1074
+ style: {
1075
+ ...s.btnGhost,
1076
+ height: 24,
1077
+ padding: "0 8px",
1078
+ fontSize: 11,
1079
+ opacity: loading ? 0.5 : 1,
1080
+ gap: 4
1081
+ },
1082
+ onClick: check,
1083
+ disabled: loading || upgrading,
1084
+ title: "\u91CD\u65B0\u68C0\u67E5 npm \u7EBF\u4E0A\u7248\u672C"
1085
+ },
1086
+ React.createElement(Icons.refresh),
1087
+ loading ? "\u68C0\u67E5\u4E2D\u2026" : "\u68C0\u67E5\u66F4\u65B0"
1088
+ )
1089
+ ),
1090
+ links
1091
+ ),
1092
+ // 发现新版本高亮卡片(支持一键直接升级)
1093
+ hasUpdate && React.createElement(
1094
+ "div",
1095
+ {
1096
+ style: {
1097
+ ...s.card,
1098
+ background: "var(--dsw-alias-state-info-bg,#eff6ff)",
1099
+ border: "1px solid var(--dsw-alias-state-info-border,#bfdbfe)",
1100
+ padding: "14px 16px",
1101
+ marginTop: 10,
1102
+ marginBottom: 0
1103
+ }
1104
+ },
1105
+ React.createElement(
1106
+ "div",
1107
+ { style: { display: "flex", alignItems: "flex-start", gap: 12 } },
1108
+ React.createElement("span", { style: { fontSize: 22 } }, "\u{1F680}"),
954
1109
  React.createElement(
955
1110
  "div",
956
- { style: { display: "flex", alignItems: "center", gap: 12 } },
957
- React.createElement("span", { style: { fontSize: 20 } }, "\u{1F389}"),
1111
+ { style: { flex: 1, minWidth: 0 } },
958
1112
  React.createElement(
959
1113
  "div",
960
- { style: { flex: 1, minWidth: 0 } },
1114
+ { style: { display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginBottom: 8 } },
961
1115
  React.createElement("div", {
962
1116
  style: {
963
1117
  fontSize: 13,
964
1118
  fontWeight: 600,
965
- color: "var(--dsw-alias-state-info-primary,#1e40af)",
966
- marginBottom: 8
1119
+ color: "var(--dsw-alias-state-info-primary,#1e40af)"
967
1120
  }
968
1121
  }, `\u53D1\u73B0\u65B0\u7248\u672C v${info.latest}\uFF08\u5F53\u524D v${info.current}\uFF09`),
969
- // 升级命令:dsh plugin 与 npx 两种,均带复制按钮
970
1122
  React.createElement(
971
- "div",
972
- { style: { display: "flex", flexDirection: "column", gap: 6 } },
973
- cmds.map(({ id, cmd }) => React.createElement(UpgradeCommandRow, { key: id, cmd }))
1123
+ "button",
1124
+ {
1125
+ style: {
1126
+ ...s.btnPri,
1127
+ height: 28,
1128
+ fontSize: 12,
1129
+ padding: "0 14px",
1130
+ background: upgradeResult?.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-brand-primary,#4f6ef7)",
1131
+ opacity: upgrading ? 0.6 : 1
1132
+ },
1133
+ onClick: handleUpgrade,
1134
+ disabled: upgrading || upgradeResult?.ok
1135
+ },
1136
+ upgrading ? React.createElement(
1137
+ "span",
1138
+ { style: { display: "inline-flex", alignItems: "center", gap: 6 } },
1139
+ React.createElement("span", { style: { animation: "spin 1s linear infinite", display: "inline-flex" } }, React.createElement(Icons.refresh)),
1140
+ "\u6B63\u5728\u81EA\u52A8\u5347\u7EA7\u2026"
1141
+ ) : upgradeResult?.ok ? "\u2713 \u5DF2\u5B8C\u6210\u5347\u7EA7" : `\u4E00\u952E\u5347\u7EA7\u5230 v${info.latest}`
974
1142
  )
975
1143
  ),
976
- React.createElement("button", {
1144
+ upgradeResult && React.createElement("div", {
977
1145
  style: {
978
- ...s.btnGhost,
979
- height: 30,
980
- padding: "0 12px",
1146
+ background: upgradeResult.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
1147
+ border: `1px solid ${upgradeResult.ok ? "var(--dsw-alias-state-success-border,#a7f3d0)" : "var(--dsw-alias-state-error-border,#fecaca)"}`,
1148
+ color: upgradeResult.ok ? "var(--dsw-alias-state-success-primary,#065f46)" : "var(--dsw-alias-state-error-primary,#991b1b)",
1149
+ padding: "8px 12px",
1150
+ borderRadius: 6,
981
1151
  fontSize: 12,
982
- opacity: loading ? 0.5 : 1,
983
- flexShrink: 0
1152
+ marginBottom: 8,
1153
+ lineHeight: 1.5
1154
+ }
1155
+ }, upgradeResult.message),
1156
+ React.createElement(
1157
+ "div",
1158
+ { style: { marginTop: 4 } },
1159
+ React.createElement("button", {
1160
+ style: { ...s.btnLink, fontSize: 11, color: "var(--dsw-alias-label-secondary,#6b7280)" },
1161
+ onClick: () => setShowManual((v) => !v)
1162
+ }, showManual ? "\u25B4 \u6298\u53E0\u624B\u52A8\u547D\u4EE4\u884C" : "\u25BE \u67E5\u770B\u624B\u52A8\u5347\u7EA7\u547D\u4EE4 (\u5982\u9700)")
1163
+ ),
1164
+ showManual && React.createElement(
1165
+ "div",
1166
+ {
1167
+ style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 8 }
984
1168
  },
985
- onClick: check,
986
- disabled: loading
987
- }, loading ? "\u2026" : "\u5237\u65B0")
1169
+ upgradeCommands(info.latest).map(
1170
+ ({ id, cmd }) => React.createElement(UpgradeCommandRow, { key: id, cmd })
1171
+ )
1172
+ )
988
1173
  )
989
- ),
990
- links
991
- );
992
- }
993
- return React.createElement(
994
- "div",
995
- { style: { marginBottom: 14, display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 8 } },
996
- React.createElement(
997
- "div",
998
- { style: { ...s.muted, display: "flex", alignItems: "center", gap: 8 } },
999
- loading && !info ? React.createElement("span", null, "\u68C0\u67E5\u66F4\u65B0\u4E2D\u2026") : info ? React.createElement("span", null, `v${info.current}${info.latest && !info.error ? " \xB7 \u5DF2\u662F\u6700\u65B0" : info.error ? " \xB7 \u68C0\u67E5\u5931\u8D25" : ""}`) : null,
1000
- info && React.createElement("button", {
1001
- style: { ...s.btnGhost, height: 22, padding: "0 8px", fontSize: 11, opacity: loading ? 0.5 : 1 },
1002
- onClick: check,
1003
- disabled: loading
1004
- }, loading ? "\u2026" : "\u91CD\u65B0\u68C0\u67E5")
1005
- ),
1006
- links
1174
+ )
1175
+ )
1007
1176
  );
1008
1177
  }
1009
1178
  var TABS = [
1010
- { id: "lan", label: "\u5C40\u57DF\u7F51", icon: "\u{1F4F6}" },
1011
- { id: "tunnel", label: "\u516C\u7F51\u96A7\u9053", icon: "\u{1F310}" },
1012
- { id: "im", label: "IM \u673A\u5668\u4EBA", icon: "\u{1F916}" }
1179
+ { id: "lan", label: "\u5C40\u57DF\u7F51", icon: Icons.lan },
1180
+ { id: "tunnel", label: "\u516C\u7F51\u96A7\u9053", icon: Icons.tunnel },
1181
+ { id: "im", label: "IM \u673A\u5668\u4EBA", icon: Icons.bot }
1013
1182
  ];
1014
1183
  function TabBar({ active, onChange, dots }) {
1015
1184
  return React.createElement(
@@ -1022,7 +1191,7 @@ function TabBar({ active, onChange, dots }) {
1022
1191
  borderBottom: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
1023
1192
  }
1024
1193
  },
1025
- TABS.map(({ id, label, icon }) => {
1194
+ TABS.map(({ id, label, icon: TabIcon }) => {
1026
1195
  const isActive = active === id;
1027
1196
  const hasDot = dots?.[id];
1028
1197
  return React.createElement(
@@ -1043,12 +1212,19 @@ function TabBar({ active, onChange, dots }) {
1043
1212
  marginBottom: -1,
1044
1213
  display: "inline-flex",
1045
1214
  alignItems: "center",
1046
- gap: 6,
1215
+ gap: 7,
1047
1216
  transition: "color .15s, border-color .15s",
1048
1217
  whiteSpace: "nowrap"
1049
1218
  }
1050
1219
  },
1051
- React.createElement("span", { style: { fontSize: 14 } }, icon),
1220
+ TabIcon && React.createElement(TabIcon, {
1221
+ style: {
1222
+ color: isActive ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-tertiary,#9ca3af)",
1223
+ width: 16,
1224
+ height: 16,
1225
+ flexShrink: 0
1226
+ }
1227
+ }),
1052
1228
  label,
1053
1229
  hasDot && React.createElement("span", {
1054
1230
  style: {
@@ -1187,9 +1363,10 @@ function BridgePanel({ rpcCall }) {
1187
1363
  );
1188
1364
  } else if (activeTab === "im") {
1189
1365
  const IM_PLATFORMS = [
1190
- { id: "wechat", label: "\u5FAE\u4FE1", icon: "\u{1F4AC}", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
1191
- { id: "qq", label: "QQ", icon: "\u{1F427}", desc: "\u5B98\u65B9\u673A\u5668\u4EBA \xB7 \u79C1\u804A/\u7FA4\u804A/\u6309\u94AE" },
1192
- { id: "feishu", label: "\u98DE\u4E66", icon: "\u{1F54A}\uFE0F", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API" }
1366
+ { id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
1367
+ { id: "qq", label: "QQ", icon: Icons.qq, brandColor: "#12B7F5", desc: "\u5B98\u65B9\u673A\u5668\u4EBA \xB7 \u79C1\u804A/\u7FA4\u804A/\u6309\u94AE" },
1368
+ { id: "feishu", label: "\u98DE\u4E66", icon: Icons.feishu, brandColor: "#00D6B9", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API" },
1369
+ { id: "telegram", label: "Telegram", icon: Icons.telegram, brandColor: "#24A1DE", desc: "\u5B98\u65B9 Bot API" }
1193
1370
  ];
1194
1371
  tabContent = React.createElement(
1195
1372
  "div",
@@ -1200,7 +1377,7 @@ function BridgePanel({ rpcCall }) {
1200
1377
  {
1201
1378
  style: { display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap" }
1202
1379
  },
1203
- IM_PLATFORMS.map(({ id, label, icon, desc }) => {
1380
+ IM_PLATFORMS.map(({ id, label, icon: IconComponent, brandColor, desc }) => {
1204
1381
  const platformData = platforms?.[id];
1205
1382
  const available = !!platformData;
1206
1383
  const active = platformData?.status === "connected" || platformData?.status === "starting" || platformData?.status === "reconnecting";
@@ -1209,7 +1386,7 @@ function BridgePanel({ rpcCall }) {
1209
1386
  {
1210
1387
  key: id,
1211
1388
  style: {
1212
- flex: "1 1 150px",
1389
+ flex: "1 1 135px",
1213
1390
  border: `1px solid ${selectedPlatform === id ? "var(--dsw-alias-brand-primary,#4f6ef7)" : active ? "var(--dsw-alias-state-success-primary,#10b981)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
1214
1391
  borderRadius: 10,
1215
1392
  padding: "12px 14px",
@@ -1226,8 +1403,8 @@ function BridgePanel({ rpcCall }) {
1226
1403
  { style: { ...s.label, fontSize: 13, display: "flex", alignItems: "center", justifyContent: "space-between" } },
1227
1404
  React.createElement(
1228
1405
  "span",
1229
- { style: { display: "flex", alignItems: "center", gap: 6 } },
1230
- React.createElement("span", { style: { fontSize: 14 } }, icon),
1406
+ { style: { display: "flex", alignItems: "center", gap: 7 } },
1407
+ IconComponent && React.createElement(IconComponent, { style: { color: brandColor, width: 18, height: 18, flexShrink: 0 } }),
1231
1408
  label
1232
1409
  ),
1233
1410
  active && React.createElement("span", {
package/client/index.js CHANGED
@@ -51,6 +51,42 @@ const s = {
51
51
  tip: { background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.6 },
52
52
  };
53
53
 
54
+ // ---- 官方品牌与 UI SVG 图标 ----
55
+
56
+ const Icons = {
57
+ wechat: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
58
+ React.createElement('path', { d: 'M8.5 2C4.36 2 1 4.91 1 8.5c0 2.01 1.05 3.81 2.69 4.97l-.69 2.06 2.45-1.22c.94.43 1.98.69 3.05.69.21 0 .42-.01.62-.03-.23-.62-.37-1.28-.37-1.97 0-3.59 3.36-6.5 7.5-6.5.21 0 .41.01.62.03C15.87 4.54 12.44 2 8.5 2zM6 6.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm5 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm7.5 3.5c-3.59 0-6.5 2.46-6.5 5.5 0 1.66.86 3.14 2.21 4.1l-.56 1.69 2.01-1c.78.36 1.64.57 2.54.57 3.59 0 6.5-2.46 6.5-5.5s-2.91-5.5-6.5-5.5zm-2 4a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z' })
59
+ ),
60
+ qq: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
61
+ React.createElement('path', { d: 'M12 2C7.58 2 4 5.37 4 9.53c0 1.95.78 3.73 2.07 5.07-.37 1.15-.99 2.19-1.8 3.08-.18.2-.04.52.23.52 2.22 0 3.99-1.07 4.9-1.86.8.25 1.66.39 2.6.39 4.42 0 8-3.37 8-7.53S16.42 2 12 2zm-3 8a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm6 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z' })
62
+ ),
63
+ feishu: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
64
+ React.createElement('path', { d: 'M12 2.5L3.5 11.2l6.8 1.8 2.2 6.5 1.8-4.7 5.2-1.4L12 2.5zm-.8 11.1l-4.1-1.1 6.5-6.6-4.2 8.3 1.8-.6z' })
65
+ ),
66
+ telegram: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
67
+ React.createElement('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 0 0-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z' })
68
+ ),
69
+ lan: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
70
+ React.createElement('path', { d: 'M12 3c-4.97 0-9 4.03-9 9 0 2.12.74 4.07 1.97 5.61L4.29 19.3a1 1 0 0 0 1.41 1.41l1.7-1.7C9.02 19.64 10.46 20 12 20c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 15c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z' })
71
+ ),
72
+ tunnel: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
73
+ React.createElement('path', { d: 'M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z' })
74
+ ),
75
+ bot: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
76
+ React.createElement('path', { d: 'M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zm-9-6c-.83 0-1.5-.67-1.5-1.5S8.17 10 9 10s1.5.67 1.5 1.5S9.83 13 9 13zm6 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z' })
77
+ ),
78
+ github: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 13, height: 13, fill: 'currentColor', ...props },
79
+ React.createElement('path', { d: 'M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z' })
80
+ ),
81
+ refresh: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
82
+ React.createElement('path', { d: 'M23 4v6h-6M1 20v-6h6' }),
83
+ React.createElement('path', { d: 'M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15' })
84
+ ),
85
+ check: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', strokeWidth: 2.5, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
86
+ React.createElement('polyline', { points: '20 6 9 17 4 12' })
87
+ ),
88
+ };
89
+
54
90
  // ---- 子组件 ----
55
91
 
56
92
  // 通用复制 hook:返回 [copied, copy],copy(text) 复制文本并短暂显示成功状态
@@ -438,7 +474,13 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
438
474
  return React.createElement('div', { style: s.card },
439
475
  React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
440
476
  React.createElement('div', null,
441
- React.createElement('div', { style: s.label }, platformName),
477
+ React.createElement('div', { style: { ...s.label, display: 'flex', alignItems: 'center', gap: 7 } },
478
+ platformId === 'wechat' && React.createElement(Icons.wechat, { style: { color: '#07C160', width: 20, height: 20 } }),
479
+ platformId === 'qq' && React.createElement(Icons.qq, { style: { color: '#12B7F5', width: 20, height: 20 } }),
480
+ platformId === 'feishu' && React.createElement(Icons.feishu, { style: { color: '#00D6B9', width: 20, height: 20 } }),
481
+ platformId === 'telegram' && React.createElement(Icons.telegram, { style: { color: '#24A1DE', width: 20, height: 20 } }),
482
+ platformName,
483
+ ),
442
484
  React.createElement('div', { style: { ...s.muted, marginTop: 2 } }, platformDesc),
443
485
  ),
444
486
  React.createElement(StatusTag, { status: platform?.status, running: connected }),
@@ -775,6 +817,10 @@ function UpgradeCommandRow({ cmd }) {
775
817
  flex: 1,
776
818
  minWidth: 0,
777
819
  wordBreak: 'break-all',
820
+ background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
821
+ padding: '4px 8px',
822
+ borderRadius: 6,
823
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
778
824
  },
779
825
  }, cmd),
780
826
  React.createElement('button', {
@@ -785,10 +831,13 @@ function UpgradeCommandRow({ cmd }) {
785
831
  );
786
832
  }
787
833
 
788
- // 版本检查 + GitHub/反馈入口
834
+ // 版本检查 + 一键升级 + GitHub/反馈入口
789
835
  function VersionBanner({ rpcCall }) {
790
836
  const [info, setInfo] = React.useState(null);
791
837
  const [loading, setLoading] = React.useState(false);
838
+ const [upgrading, setUpgrading] = React.useState(false);
839
+ const [upgradeResult, setUpgradeResult] = React.useState(null);
840
+ const [showManual, setShowManual] = React.useState(false);
792
841
 
793
842
  const check = React.useCallback(async () => {
794
843
  setLoading(true);
@@ -802,92 +851,195 @@ function VersionBanner({ rpcCall }) {
802
851
 
803
852
  React.useEffect(() => { check(); }, [check]);
804
853
 
805
- // 只有 npm 上的版本严格大于当前版本才算有新版本
806
854
  const hasUpdate = info?.latest && info?.current && !info.error && semverGt(info.latest, info.current);
855
+ const isLatest = info?.latest && info?.current && !info.error && !semverGt(info.latest, info.current);
856
+
857
+ const handleUpgrade = React.useCallback(async () => {
858
+ if (!info?.latest || upgrading) return;
859
+ setUpgrading(true);
860
+ setUpgradeResult(null);
861
+ try {
862
+ const r = await rpcCall(BRIDGE_ENDPOINTS.upgradePlugin, { version: info.latest });
863
+ if (r?.ok && r.value?.ok) {
864
+ setUpgradeResult({ ok: true, message: `已成功升级到 v${info.latest}!请重启 DSH 服务使新版本生效。` });
865
+ setTimeout(() => check(), 3000);
866
+ } else {
867
+ setUpgradeResult({ ok: false, message: r?.value?.error || r?.error?.message || '升级失败' });
868
+ setShowManual(true);
869
+ }
870
+ } catch (e) {
871
+ setUpgradeResult({ ok: false, message: e.message || '升级请求失败' });
872
+ setShowManual(true);
873
+ } finally {
874
+ setUpgrading(false);
875
+ }
876
+ }, [info?.latest, upgrading, rpcCall, check]);
807
877
 
808
- // 链接按钮组:GitHub + 更新日志 + 反馈 Bug
809
878
  const links = React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' } },
810
879
  React.createElement('a', {
811
880
  href: GITHUB_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
812
- }, 'GitHub'),
881
+ }, React.createElement(Icons.github), 'GitHub'),
813
882
  React.createElement('a', {
814
883
  href: RELEASES_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
815
- }, '📋 更新日志'),
884
+ }, '更新日志'),
816
885
  React.createElement('a', {
817
886
  href: ISSUES_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
818
- }, '🐛 反馈 Bug'),
887
+ }, '反馈 Issue'),
819
888
  );
820
889
 
821
- // 有新版本:信息卡片
822
- if (hasUpdate) {
823
- const cmds = upgradeCommands(info.latest);
824
- return React.createElement('div', { style: { marginBottom: 16 } },
825
- React.createElement('div', {
826
- style: {
827
- ...s.card,
828
- background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
829
- border: '1px solid var(--dsw-alias-state-info-border,#bfdbfe)',
830
- padding: '12px 16px',
831
- marginBottom: 8,
890
+ return React.createElement('div', { style: { marginBottom: 16 } },
891
+ // 顶部状态行:版本徽标 + 刷新按钮 + 链接组
892
+ React.createElement('div', {
893
+ style: {
894
+ display: 'flex',
895
+ alignItems: 'center',
896
+ justifyContent: 'space-between',
897
+ flexWrap: 'wrap',
898
+ gap: 8,
899
+ },
900
+ },
901
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
902
+ // 版本状态徽标
903
+ React.createElement('span', {
904
+ style: {
905
+ ...s.tag,
906
+ background: hasUpdate
907
+ ? 'var(--dsw-alias-state-info-bg,#eff6ff)'
908
+ : isLatest
909
+ ? 'var(--dsw-alias-state-success-bg,#ecfdf5)'
910
+ : 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
911
+ color: hasUpdate
912
+ ? 'var(--dsw-alias-state-info-primary,#2563eb)'
913
+ : isLatest
914
+ ? 'var(--dsw-alias-state-success-primary,#059669)'
915
+ : 'var(--dsw-alias-label-secondary,#6b7280)',
916
+ padding: '3px 10px',
917
+ fontSize: 12,
918
+ fontWeight: 500,
919
+ display: 'inline-flex',
920
+ alignItems: 'center',
921
+ gap: 5,
922
+ },
832
923
  },
924
+ loading
925
+ ? React.createElement('span', {
926
+ style: { display: 'inline-flex', alignItems: 'center' },
927
+ }, React.createElement(Icons.refresh))
928
+ : isLatest
929
+ ? React.createElement(Icons.check)
930
+ : null,
931
+ info ? `v${info.current}` : '版本检查中…',
932
+ isLatest && React.createElement('span', { style: { opacity: 0.85, fontSize: 11, fontWeight: 400 } }, '· 已是最新'),
933
+ hasUpdate && React.createElement('span', { style: { fontWeight: 600, fontSize: 11 } }, `➔ v${info.latest}`),
934
+ info?.error && React.createElement('span', { style: { color: 'var(--dsw-alias-state-warn-primary,#d97706)', fontSize: 11 } }, '(网络超时)'),
935
+ ),
936
+ // 刷新检查按钮
937
+ React.createElement('button', {
938
+ style: {
939
+ ...s.btnGhost,
940
+ height: 24,
941
+ padding: '0 8px',
942
+ fontSize: 11,
943
+ opacity: loading ? 0.5 : 1,
944
+ gap: 4,
945
+ },
946
+ onClick: check,
947
+ disabled: loading || upgrading,
948
+ title: '重新检查 npm 线上版本',
949
+ },
950
+ React.createElement(Icons.refresh),
951
+ loading ? '检查中…' : '检查更新',
952
+ ),
953
+ ),
954
+ links,
955
+ ),
956
+
957
+ // 发现新版本高亮卡片(支持一键直接升级)
958
+ hasUpdate && React.createElement('div', {
959
+ style: {
960
+ ...s.card,
961
+ background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
962
+ border: '1px solid var(--dsw-alias-state-info-border,#bfdbfe)',
963
+ padding: '14px 16px',
964
+ marginTop: 10,
965
+ marginBottom: 0,
833
966
  },
834
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12 } },
835
- React.createElement('span', { style: { fontSize: 20 } }, '🎉'),
836
- React.createElement('div', { style: { flex: 1, minWidth: 0 } },
967
+ },
968
+ React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 12 } },
969
+ React.createElement('span', { style: { fontSize: 22 } }, '🚀'),
970
+ React.createElement('div', { style: { flex: 1, minWidth: 0 } },
971
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8, marginBottom: 8 } },
837
972
  React.createElement('div', {
838
973
  style: {
839
974
  fontSize: 13,
840
975
  fontWeight: 600,
841
976
  color: 'var(--dsw-alias-state-info-primary,#1e40af)',
842
- marginBottom: 8,
843
977
  },
844
978
  }, `发现新版本 v${info.latest}(当前 v${info.current})`),
845
- // 升级命令:dsh plugin 与 npx 两种,均带复制按钮
846
- React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
847
- cmds.map(({ id, cmd }) => React.createElement(UpgradeCommandRow, { key: id, cmd })),
979
+ React.createElement('button', {
980
+ style: {
981
+ ...s.btnPri,
982
+ height: 28,
983
+ fontSize: 12,
984
+ padding: '0 14px',
985
+ background: upgradeResult?.ok
986
+ ? 'var(--dsw-alias-state-success-primary,#059669)'
987
+ : 'var(--dsw-alias-brand-primary,#4f6ef7)',
988
+ opacity: upgrading ? 0.6 : 1,
989
+ },
990
+ onClick: handleUpgrade,
991
+ disabled: upgrading || upgradeResult?.ok,
992
+ },
993
+ upgrading
994
+ ? React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
995
+ React.createElement('span', { style: { animation: 'spin 1s linear infinite', display: 'inline-flex' } }, React.createElement(Icons.refresh)),
996
+ '正在自动升级…',
997
+ )
998
+ : upgradeResult?.ok
999
+ ? '✓ 已完成升级'
1000
+ : `一键升级到 v${info.latest}`
848
1001
  ),
849
1002
  ),
850
- React.createElement('button', {
1003
+
1004
+ upgradeResult && React.createElement('div', {
851
1005
  style: {
852
- ...s.btnGhost,
853
- height: 30,
854
- padding: '0 12px',
1006
+ background: upgradeResult.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
1007
+ border: `1px solid ${upgradeResult.ok ? 'var(--dsw-alias-state-success-border,#a7f3d0)' : 'var(--dsw-alias-state-error-border,#fecaca)'}`,
1008
+ color: upgradeResult.ok ? 'var(--dsw-alias-state-success-primary,#065f46)' : 'var(--dsw-alias-state-error-primary,#991b1b)',
1009
+ padding: '8px 12px',
1010
+ borderRadius: 6,
855
1011
  fontSize: 12,
856
- opacity: loading ? 0.5 : 1,
857
- flexShrink: 0,
1012
+ marginBottom: 8,
1013
+ lineHeight: 1.5,
858
1014
  },
859
- onClick: check,
860
- disabled: loading,
861
- }, loading ? '…' : '刷新'),
1015
+ }, upgradeResult.message),
1016
+
1017
+ React.createElement('div', { style: { marginTop: 4 } },
1018
+ React.createElement('button', {
1019
+ style: { ...s.btnLink, fontSize: 11, color: 'var(--dsw-alias-label-secondary,#6b7280)' },
1020
+ onClick: () => setShowManual(v => !v),
1021
+ }, showManual ? '▴ 折叠手动命令行' : '▾ 查看手动升级命令 (如需)'),
1022
+ ),
1023
+
1024
+ showManual && React.createElement('div', {
1025
+ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 },
1026
+ },
1027
+ upgradeCommands(info.latest).map(({ id, cmd }) =>
1028
+ React.createElement(UpgradeCommandRow, { key: id, cmd })
1029
+ ),
1030
+ ),
862
1031
  ),
863
1032
  ),
864
- links,
865
- );
866
- }
867
-
868
- // 正常状态:版本信息一行 + 链接
869
- return React.createElement('div', { style: { marginBottom: 14, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 } },
870
- React.createElement('div', { style: { ...s.muted, display: 'flex', alignItems: 'center', gap: 8 } },
871
- loading && !info
872
- ? React.createElement('span', null, '检查更新中…')
873
- : info
874
- ? React.createElement('span', null, `v${info.current}${info.latest && !info.error ? ' · 已是最新' : info.error ? ' · 检查失败' : ''}`)
875
- : null,
876
- info && React.createElement('button', {
877
- style: { ...s.btnGhost, height: 22, padding: '0 8px', fontSize: 11, opacity: loading ? 0.5 : 1 },
878
- onClick: check, disabled: loading,
879
- }, loading ? '…' : '重新检查'),
880
1033
  ),
881
- links,
882
1034
  );
883
1035
  }
884
1036
 
885
1037
  // ---- Tab Bar ----
886
1038
 
887
1039
  const TABS = [
888
- { id: 'lan', label: '局域网', icon: '📶' },
889
- { id: 'tunnel', label: '公网隧道', icon: '🌐' },
890
- { id: 'im', label: 'IM 机器人', icon: '🤖' },
1040
+ { id: 'lan', label: '局域网', icon: Icons.lan },
1041
+ { id: 'tunnel', label: '公网隧道', icon: Icons.tunnel },
1042
+ { id: 'im', label: 'IM 机器人', icon: Icons.bot },
891
1043
  ];
892
1044
 
893
1045
  function TabBar({ active, onChange, dots }) {
@@ -897,7 +1049,7 @@ function TabBar({ active, onChange, dots }) {
897
1049
  borderBottom: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
898
1050
  },
899
1051
  },
900
- TABS.map(({ id, label, icon }) => {
1052
+ TABS.map(({ id, label, icon: TabIcon }) => {
901
1053
  const isActive = active === id;
902
1054
  const hasDot = dots?.[id];
903
1055
  return React.createElement('button', {
@@ -913,12 +1065,17 @@ function TabBar({ active, onChange, dots }) {
913
1065
  ? '2px solid var(--dsw-alias-brand-primary,#4f6ef7)'
914
1066
  : '2px solid transparent',
915
1067
  marginBottom: -1,
916
- display: 'inline-flex', alignItems: 'center', gap: 6,
1068
+ display: 'inline-flex', alignItems: 'center', gap: 7,
917
1069
  transition: 'color .15s, border-color .15s',
918
1070
  whiteSpace: 'nowrap',
919
1071
  },
920
1072
  },
921
- React.createElement('span', { style: { fontSize: 14 } }, icon),
1073
+ TabIcon && React.createElement(TabIcon, {
1074
+ style: {
1075
+ color: isActive ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : 'var(--dsw-alias-label-tertiary,#9ca3af)',
1076
+ width: 16, height: 16, flexShrink: 0,
1077
+ },
1078
+ }),
922
1079
  label,
923
1080
  hasDot && React.createElement('span', {
924
1081
  style: {
@@ -1062,9 +1219,10 @@ function BridgePanel({ rpcCall }) {
1062
1219
  } else if (activeTab === 'im') {
1063
1220
  // 从 listPlatforms 动态生成平台列表
1064
1221
  const IM_PLATFORMS = [
1065
- { id: 'wechat', label: '微信', icon: '💬', desc: 'ClawBot 扫码直连 · 无需公网' },
1066
- { id: 'qq', label: 'QQ', icon: '🐧', desc: '官方机器人 · 私聊/群聊/按钮' },
1067
- { id: 'feishu', label: '飞书', icon: '🕊️', desc: '官方事件回调 API' },
1222
+ { id: 'wechat', label: '微信', icon: Icons.wechat, brandColor: '#07C160', desc: 'ClawBot 扫码直连 · 无需公网' },
1223
+ { id: 'qq', label: 'QQ', icon: Icons.qq, brandColor: '#12B7F5', desc: '官方机器人 · 私聊/群聊/按钮' },
1224
+ { id: 'feishu', label: '飞书', icon: Icons.feishu, brandColor: '#00D6B9', desc: '官方事件回调 API' },
1225
+ { id: 'telegram', label: 'Telegram', icon: Icons.telegram, brandColor: '#24A1DE', desc: '官方 Bot API' },
1068
1226
  ];
1069
1227
 
1070
1228
  tabContent = React.createElement('div', null,
@@ -1072,7 +1230,7 @@ function BridgePanel({ rpcCall }) {
1072
1230
  React.createElement('div', {
1073
1231
  style: { display: 'flex', gap: 10, marginBottom: 16, flexWrap: 'wrap' },
1074
1232
  },
1075
- IM_PLATFORMS.map(({ id, label, icon, desc }) => {
1233
+ IM_PLATFORMS.map(({ id, label, icon: IconComponent, brandColor, desc }) => {
1076
1234
  const platformData = platforms?.[id];
1077
1235
  const available = !!platformData;
1078
1236
  const active = platformData?.status === 'connected' || platformData?.status === 'starting' || platformData?.status === 'reconnecting';
@@ -1080,7 +1238,7 @@ function BridgePanel({ rpcCall }) {
1080
1238
  return React.createElement('div', {
1081
1239
  key: id,
1082
1240
  style: {
1083
- flex: '1 1 150px',
1241
+ flex: '1 1 135px',
1084
1242
  border: `1px solid ${selectedPlatform === id ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : active ? 'var(--dsw-alias-state-success-primary,#10b981)' : 'var(--dsw-alias-border-l2,#e5e7eb)'}`,
1085
1243
  borderRadius: 10,
1086
1244
  padding: '12px 14px',
@@ -1093,8 +1251,8 @@ function BridgePanel({ rpcCall }) {
1093
1251
  onClick: available ? () => setSelectedPlatform(id) : undefined,
1094
1252
  },
1095
1253
  React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
1096
- React.createElement('span', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
1097
- React.createElement('span', { style: { fontSize: 14 } }, icon),
1254
+ React.createElement('span', { style: { display: 'flex', alignItems: 'center', gap: 7 } },
1255
+ IconComponent && React.createElement(IconComponent, { style: { color: brandColor, width: 18, height: 18, flexShrink: 0 } }),
1098
1256
  label,
1099
1257
  ),
1100
1258
  active && React.createElement('span', {
@@ -11,6 +11,7 @@ export const BRIDGE_ENDPOINTS = {
11
11
  resetCloudflared: 'resetCloudflared',
12
12
  saveCustomTunnelConfig: 'saveCustomTunnelConfig',
13
13
  checkVersion: 'checkVersion',
14
+ upgradePlugin: 'upgradePlugin',
14
15
  // 平台管理器(多 IM 平台统一接口)
15
16
  listPlatforms: 'listPlatforms',
16
17
  platformLogin: 'platformLogin',
package/lib/bridge-rpc.js CHANGED
@@ -123,6 +123,11 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
123
123
  return ok(result);
124
124
  }
125
125
 
126
+ if (endpoint === BRIDGE_ENDPOINTS.upgradePlugin) {
127
+ const result = await service.upgradePlugin(payload);
128
+ return ok(result);
129
+ }
130
+
126
131
  // ---- 平台管理器(多 IM 平台)----
127
132
 
128
133
  if (endpoint === BRIDGE_ENDPOINTS.listPlatforms) {
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ import { join, dirname } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { readFileSync } from 'node:fs';
14
14
  import { readFile, writeFile, mkdir, unlink } from 'node:fs/promises';
15
+ import { exec } from 'node:child_process';
15
16
  import QRCode from 'qrcode';
16
17
  import { installBridgeRpc } from './bridge-rpc.js';
17
18
  import { CustomTunnelClient } from './tunnel-client.mjs';
@@ -406,24 +407,64 @@ class BridgeService {
406
407
  this.cloudflaredState = { phase: 'idle', detail: '' };
407
408
  }
408
409
 
409
- // 检查 npm 上是否有新版本
410
+ // 检查 npm 上是否有新版本(优先国内高速镜像 npmmirror,降级 npmjs 官方源)
410
411
  async checkVersion() {
411
- return new Promise((resolve) => {
412
- const req = httpsGet('https://registry.npmjs.org/@wenbin_wb/dsh-bridge/latest', { timeout: 8000 }, (res) => {
412
+ const fetchRegistry = (url, timeoutMs = 4000) => new Promise((resolve, reject) => {
413
+ const req = httpsGet(url, { timeout: timeoutMs }, (res) => {
414
+ if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
413
415
  const chunks = [];
414
416
  res.on('data', (c) => chunks.push(c));
415
417
  res.on('end', () => {
416
418
  try {
417
419
  const data = JSON.parse(Buffer.concat(chunks).toString());
418
- resolve({ current: VERSION, latest: data.version ?? null });
419
- } catch {
420
- resolve({ current: VERSION, latest: null, error: '解析失败' });
420
+ resolve(data.version ?? null);
421
+ } catch (e) {
422
+ reject(e);
421
423
  }
422
424
  });
423
425
  });
424
- req.on('error', (e) => resolve({ current: VERSION, latest: null, error: e.message }));
425
- req.on('timeout', () => { req.destroy(); resolve({ current: VERSION, latest: null, error: '超时' }); });
426
+ req.on('error', reject);
427
+ req.on('timeout', () => { req.destroy(); reject(new Error('请求超时')); });
426
428
  });
429
+
430
+ try {
431
+ const latest = await fetchRegistry('https://registry.npmmirror.com/@wenbin_wb/dsh-bridge/latest', 3500)
432
+ .catch(() => fetchRegistry('https://registry.npmjs.org/@wenbin_wb/dsh-bridge/latest', 5000));
433
+ return { current: VERSION, latest: latest ?? null };
434
+ } catch (e) {
435
+ return { current: VERSION, latest: null, error: e.message ?? '检查失败' };
436
+ }
437
+ }
438
+
439
+ // 一键直接升级插件(执行 dsh plugin / pnpm / npm 自动升级)
440
+ async upgradePlugin({ version } = {}) {
441
+ const targetVersion = version ? String(version).trim() : 'latest';
442
+ const pkgSpec = `@wenbin_wb/dsh-bridge@${targetVersion}`;
443
+ const commands = [
444
+ `dsh plugin --profile web add ${pkgSpec}`,
445
+ `npx --yes @deepseek-ai/dsh plugin --profile web add ${pkgSpec}`,
446
+ `npm install ${pkgSpec}`,
447
+ ];
448
+
449
+ let lastError = null;
450
+ let output = '';
451
+
452
+ for (const cmd of commands) {
453
+ try {
454
+ const res = await new Promise((resolve, reject) => {
455
+ exec(cmd, { timeout: 90000, windowsHide: true }, (err, stdout, stderr) => {
456
+ if (err) return reject(new Error(stderr || err.message));
457
+ resolve({ stdout, stderr });
458
+ });
459
+ });
460
+ output = res.stdout || res.stderr || '升级成功';
461
+ return { ok: true, command: cmd, output, version: targetVersion };
462
+ } catch (err) {
463
+ lastError = err;
464
+ }
465
+ }
466
+
467
+ return { ok: false, error: lastError?.message ?? '升级命令执行失败', version: targetVersion };
427
468
  }
428
469
 
429
470
  async dispose() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 Bot(多工作区/会话持久化/媒体/审批),无需自己搭公网服务器。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",