@lanbaolu/dsh-wechat-bridge 0.5.0 → 0.7.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/lib/client.js CHANGED
@@ -17,7 +17,12 @@ window.__ModuleLoader__.load({
17
17
  background: "var(--surface-2, rgba(127,127,127,.06))",
18
18
  border: "1px solid var(--border-color, rgba(127,127,127,.18))",
19
19
  borderRadius: 10,
20
- margin: "8px 0"
20
+ margin: "8px 0",
21
+ "--text-1": "var(--dsw-alias-label-primary, #1f2328)",
22
+ "--surface-2": "var(--dsw-alias-bg-module-platform, rgba(127,127,127,.06))",
23
+ "--surface-3": "var(--dsw-alias-bg-layer-2, rgba(0,0,0,.04))",
24
+ "--border-color": "var(--dsw-alias-border-l2, rgba(127,127,127,.18))",
25
+ "--button-bg": "var(--dsw-alias-bg-layer-1, #fff)"
21
26
  };
22
27
  const titleStyle = {
23
28
  fontWeight: 600,
@@ -71,6 +76,50 @@ window.__ModuleLoader__.load({
71
76
  fontSize: 12,
72
77
  opacity: .7
73
78
  };
79
+ const TRUST_MODE_LABELS = {
80
+ "owner-only": "仅本机主人(默认)",
81
+ bootstrap: "首位陌生人自动入集(一次性)",
82
+ manual: "仅手动添加的人"
83
+ };
84
+ const CALM_DEFAULTS = {
85
+ enabled: true,
86
+ silenceMin: "5",
87
+ intervalMin: "5",
88
+ maxCount: "0",
89
+ messages: ""
90
+ };
91
+ function calmFromConfig(calm) {
92
+ return {
93
+ enabled: calm?.enabled !== false,
94
+ silenceMin: calm?.silenceMs && calm.silenceMs > 0 ? String(Math.round(calm.silenceMs / 6e4)) : "5",
95
+ intervalMin: calm?.intervalMs && calm.intervalMs > 0 ? String(Math.round(calm.intervalMs / 6e4)) : "5",
96
+ maxCount: calm?.maxCount !== void 0 ? String(calm.maxCount) : "0",
97
+ messages: calm?.messages?.length ? calm.messages.join("\n") : ""
98
+ };
99
+ }
100
+ function calmToConfig(ui) {
101
+ const toMin = (v) => {
102
+ const n = Number(v);
103
+ return Number.isFinite(n) && n > 0 ? Math.round(n * 6e4) : void 0;
104
+ };
105
+ const maxCount = Number(ui.maxCount);
106
+ const messages = ui.messages.split("\n").map((s) => s.trim()).filter(Boolean);
107
+ return {
108
+ enabled: ui.enabled,
109
+ silenceMs: toMin(ui.silenceMin),
110
+ intervalMs: toMin(ui.intervalMin),
111
+ maxCount: Number.isFinite(maxCount) && maxCount >= 0 ? Math.round(maxCount) : void 0,
112
+ messages: messages.length > 0 ? messages : void 0
113
+ };
114
+ }
115
+ function formatTime(ms) {
116
+ if (!ms) return "从未活跃";
117
+ try {
118
+ return new Date(ms).toLocaleString("zh-CN");
119
+ } catch {
120
+ return "未知";
121
+ }
122
+ }
74
123
  function WechatBridgePanel(_props) {
75
124
  const [output, setOutput] = (0, react.useState)("加载中…");
76
125
  const [error, setError] = (0, react.useState)(null);
@@ -89,6 +138,16 @@ window.__ModuleLoader__.load({
89
138
  const [projectMessage, setProjectMessage] = (0, react.useState)("");
90
139
  const [projectError, setProjectError] = (0, react.useState)("");
91
140
  const [notifyStatus, setNotifyStatus] = (0, react.useState)(null);
141
+ const [calm, setCalm] = (0, react.useState)(CALM_DEFAULTS);
142
+ const [calmBusy, setCalmBusy] = (0, react.useState)(false);
143
+ const [calmMessage, setCalmMessage] = (0, react.useState)("");
144
+ const [calmError, setCalmError] = (0, react.useState)("");
145
+ const [trust, setTrust] = (0, react.useState)(null);
146
+ const [trustBusy, setTrustBusy] = (0, react.useState)(false);
147
+ const [trustMessage, setTrustMessage] = (0, react.useState)("");
148
+ const [trustError, setTrustError] = (0, react.useState)("");
149
+ const [newTrustId, setNewTrustId] = (0, react.useState)("");
150
+ const [newTrustNote, setNewTrustNote] = (0, react.useState)("");
92
151
  const refresh = (0, react.useCallback)(async () => {
93
152
  try {
94
153
  const [statusRes, projectsRes, notifyRes] = await Promise.all([
@@ -111,7 +170,40 @@ window.__ModuleLoader__.load({
111
170
  } catch (err) {
112
171
  setError(err instanceof Error ? err.message : String(err));
113
172
  }
173
+ try {
174
+ const trustRes = await fetch(`${API_BASE}/trust`, { cache: "no-store" });
175
+ if (trustRes.ok) {
176
+ const trustData = await trustRes.json();
177
+ if (trustData && typeof trustData === "object" && trustData.ok) setTrust(trustData);
178
+ }
179
+ } catch {}
180
+ try {
181
+ const cfgRes = await fetch(`${API_BASE}/config`, { cache: "no-store" });
182
+ if (cfgRes.ok) {
183
+ const cfgData = await cfgRes.json();
184
+ if (cfgData && typeof cfgData === "object" && cfgData.ok) setCalm(calmFromConfig(cfgData.calm));
185
+ }
186
+ } catch {}
114
187
  }, []);
188
+ async function saveCalm() {
189
+ setCalmBusy(true);
190
+ setCalmError("");
191
+ setCalmMessage("");
192
+ try {
193
+ const res = await fetch(`${API_BASE}/config`, {
194
+ method: "POST",
195
+ headers: { "Content-Type": "application/json" },
196
+ body: JSON.stringify({ calm: calmToConfig(calm) })
197
+ });
198
+ const data = await res.json().catch(() => null);
199
+ if (!res.ok || !data?.ok) throw new Error(data?.error || `HTTP ${res.status}`);
200
+ setCalmMessage("已保存,下次安抚时生效(最长延迟数秒)。");
201
+ } catch (err) {
202
+ setCalmError(err instanceof Error ? err.message : String(err));
203
+ } finally {
204
+ setCalmBusy(false);
205
+ }
206
+ }
115
207
  (0, react.useEffect)(() => {
116
208
  refresh();
117
209
  }, [refresh]);
@@ -203,6 +295,56 @@ window.__ModuleLoader__.load({
203
295
  setProjectBusy(false);
204
296
  }
205
297
  }
298
+ function applyTrustResponse(data) {
299
+ if (data.ok === false) throw new Error(data.error || "操作失败");
300
+ setTrust({
301
+ mode: data.mode,
302
+ bootstrapConsumed: data.bootstrapConsumed,
303
+ owner: data.owner,
304
+ notifyRejected: data.notifyRejected,
305
+ trusted: Array.isArray(data.trusted) ? data.trusted : []
306
+ });
307
+ }
308
+ async function trustRequest(path, body, okMessage) {
309
+ setTrustBusy(true);
310
+ setTrustError("");
311
+ setTrustMessage("");
312
+ try {
313
+ const res = await fetch(`${API_BASE}/${path}`, {
314
+ method: "POST",
315
+ headers: { "Content-Type": "application/json" },
316
+ body: JSON.stringify(body)
317
+ });
318
+ const data = await res.json().catch(() => null);
319
+ if (!res.ok || !data?.ok) throw new Error(data?.error || `HTTP ${res.status}`);
320
+ applyTrustResponse(data);
321
+ setTrustMessage(okMessage);
322
+ } catch (err) {
323
+ setTrustError(err instanceof Error ? err.message : String(err));
324
+ } finally {
325
+ setTrustBusy(false);
326
+ }
327
+ }
328
+ async function addTrustedUser() {
329
+ const id = newTrustId.trim();
330
+ if (!id) return;
331
+ await trustRequest("trust/add", {
332
+ userId: id,
333
+ note: newTrustNote.trim() || void 0
334
+ }, `已添加:${id}`);
335
+ setNewTrustId("");
336
+ setNewTrustNote("");
337
+ }
338
+ function removeTrustedUser(userId) {
339
+ if (!window.confirm(`确认吊销 ${userId}?其后续消息将被拒绝,已有会话历史保留。`)) return;
340
+ trustRequest("trust/remove", { userId }, `已吊销:${userId}`);
341
+ }
342
+ function changeTrustMode(mode) {
343
+ trustRequest("trust/config", { mode }, `信任模式已切换为:${TRUST_MODE_LABELS[mode]}`);
344
+ }
345
+ function toggleNotifyRejected(enabled) {
346
+ trustRequest("trust/config", { notifyRejected: enabled }, enabled ? "已开启陌生人联系提醒" : "已关闭陌生人联系提醒");
347
+ }
206
348
  (0, react.useEffect)(() => {
207
349
  if (setupPhase !== "qr" || !qrcodeId) return;
208
350
  let cancelled = false;
@@ -440,6 +582,364 @@ window.__ModuleLoader__.load({
440
582
  children: "守护进程未运行,暂无数据。"
441
583
  })]
442
584
  }),
585
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
586
+ style: { marginBottom: 12 },
587
+ children: [
588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
589
+ style: {
590
+ ...titleStyle,
591
+ marginBottom: 4
592
+ },
593
+ children: "⏳ 超时安抚"
594
+ }),
595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
596
+ style: {
597
+ fontSize: 12,
598
+ opacity: .7,
599
+ marginBottom: 8
600
+ },
601
+ children: "DSH 长时间没有产出消息时,主动发一条\"还在处理\"的安抚消息。改配置后即时生效(最长延迟数秒)。"
602
+ }),
603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
604
+ style: {
605
+ display: "flex",
606
+ alignItems: "center",
607
+ gap: 6,
608
+ fontSize: 12,
609
+ marginBottom: 8
610
+ },
611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
612
+ type: "checkbox",
613
+ checked: calm.enabled,
614
+ onChange: (e) => setCalm((c) => ({
615
+ ...c,
616
+ enabled: e.target.checked
617
+ }))
618
+ }), "启用安抚消息"]
619
+ }),
620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
621
+ style: {
622
+ display: "flex",
623
+ gap: 12,
624
+ flexWrap: "wrap",
625
+ alignItems: "center",
626
+ marginBottom: 8
627
+ },
628
+ children: [
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
630
+ style: { fontSize: 12 },
631
+ children: ["首次静默(分钟)", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
632
+ type: "number",
633
+ min: 1,
634
+ value: calm.silenceMin,
635
+ onChange: (e) => setCalm((c) => ({
636
+ ...c,
637
+ silenceMin: e.target.value
638
+ })),
639
+ style: {
640
+ ...inputStyle,
641
+ marginLeft: 6,
642
+ minWidth: 70
643
+ }
644
+ })]
645
+ }),
646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
647
+ style: { fontSize: 12 },
648
+ children: ["重复间隔(分钟)", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
649
+ type: "number",
650
+ min: 1,
651
+ value: calm.intervalMin,
652
+ onChange: (e) => setCalm((c) => ({
653
+ ...c,
654
+ intervalMin: e.target.value
655
+ })),
656
+ style: {
657
+ ...inputStyle,
658
+ marginLeft: 6,
659
+ minWidth: 70
660
+ }
661
+ })]
662
+ }),
663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
664
+ style: { fontSize: 12 },
665
+ children: ["每轮上限(次,0=不限)", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
666
+ type: "number",
667
+ min: 0,
668
+ value: calm.maxCount,
669
+ onChange: (e) => setCalm((c) => ({
670
+ ...c,
671
+ maxCount: e.target.value
672
+ })),
673
+ style: {
674
+ ...inputStyle,
675
+ marginLeft: 6,
676
+ minWidth: 70
677
+ }
678
+ })]
679
+ })
680
+ ]
681
+ }),
682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
683
+ style: {
684
+ fontSize: 12,
685
+ display: "block",
686
+ marginBottom: 6
687
+ },
688
+ children: ["自定义文案(每行一条,留空用内置默认;每次随机取一条)", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
689
+ value: calm.messages,
690
+ onChange: (e) => setCalm((c) => ({
691
+ ...c,
692
+ messages: e.target.value
693
+ })),
694
+ rows: 3,
695
+ style: {
696
+ ...inputStyle,
697
+ display: "block",
698
+ marginTop: 4,
699
+ minWidth: "100%",
700
+ boxSizing: "border-box",
701
+ resize: "vertical",
702
+ fontFamily: "inherit"
703
+ }
704
+ })]
705
+ }),
706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
707
+ style: {
708
+ display: "flex",
709
+ gap: 8,
710
+ alignItems: "center"
711
+ },
712
+ children: [
713
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
714
+ type: "button",
715
+ style: buttonStyle,
716
+ disabled: calmBusy,
717
+ onClick: () => void saveCalm(),
718
+ children: "保存安抚设置"
719
+ }),
720
+ calmMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
721
+ role: "status",
722
+ style: {
723
+ color: "#2f9e44",
724
+ fontSize: 12
725
+ },
726
+ children: calmMessage
727
+ }),
728
+ calmError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
729
+ role: "alert",
730
+ style: {
731
+ color: "#e5484d",
732
+ fontSize: 12
733
+ },
734
+ children: calmError
735
+ })
736
+ ]
737
+ })
738
+ ]
739
+ }),
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
741
+ style: { marginBottom: 12 },
742
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
743
+ style: {
744
+ ...titleStyle,
745
+ marginBottom: 4
746
+ },
747
+ children: "🔐 信任用户(多用户)"
748
+ }), !trust ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
749
+ style: {
750
+ fontSize: 12,
751
+ opacity: .7
752
+ },
753
+ children: "加载中…"
754
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
756
+ style: {
757
+ fontSize: 12,
758
+ opacity: .85,
759
+ marginBottom: 6
760
+ },
761
+ children: ["信任模式", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
762
+ value: trust.mode,
763
+ onChange: (e) => changeTrustMode(e.target.value),
764
+ disabled: trustBusy,
765
+ style: {
766
+ ...inputStyle,
767
+ marginLeft: 8,
768
+ minWidth: 240
769
+ },
770
+ "aria-label": "选择信任模式",
771
+ children: [
772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
773
+ value: "owner-only",
774
+ children: TRUST_MODE_LABELS["owner-only"]
775
+ }),
776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
777
+ value: "bootstrap",
778
+ children: TRUST_MODE_LABELS.bootstrap
779
+ }),
780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
781
+ value: "manual",
782
+ children: TRUST_MODE_LABELS.manual
783
+ })
784
+ ]
785
+ })]
786
+ }),
787
+ trust.mode === "bootstrap" && trust.bootstrapConsumed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
788
+ style: {
789
+ fontSize: 12,
790
+ color: "#b7791f",
791
+ marginBottom: 6
792
+ },
793
+ children: "⚠️ bootstrap 首次名额已用完,后续陌生人不会再自动入集,请用下方表单手动添加或切到 manual。"
794
+ }),
795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
796
+ style: {
797
+ display: "flex",
798
+ alignItems: "center",
799
+ gap: 6,
800
+ fontSize: 12,
801
+ marginBottom: 8
802
+ },
803
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
804
+ type: "checkbox",
805
+ checked: trust.notifyRejected,
806
+ disabled: trustBusy,
807
+ onChange: (e) => toggleNotifyRejected(e.target.checked)
808
+ }), "陌生人尝试联系时向 owner 推送提醒"]
809
+ }),
810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
811
+ style: {
812
+ fontSize: 12,
813
+ opacity: .7,
814
+ marginBottom: 8
815
+ },
816
+ children: [
817
+ "owner:",
818
+ trust.owner || "(未绑定)",
819
+ " · 信任用户:",
820
+ trust.trusted.length,
821
+ " 个"
822
+ ]
823
+ }),
824
+ trust.trusted.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
825
+ style: {
826
+ marginBottom: 8,
827
+ display: "flex",
828
+ flexDirection: "column",
829
+ gap: 6
830
+ },
831
+ children: trust.trusted.map((u) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
+ style: {
833
+ display: "flex",
834
+ alignItems: "center",
835
+ gap: 8,
836
+ padding: "6px 8px",
837
+ background: "var(--surface-3, rgba(127,127,127,.05))",
838
+ border: "1px solid var(--border-color, rgba(127,127,127,.12))",
839
+ borderRadius: 6
840
+ },
841
+ children: [
842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
843
+ style: { fontWeight: 600 },
844
+ children: u.userId
845
+ }),
846
+ u.note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
847
+ style: {
848
+ opacity: .7,
849
+ fontSize: 12
850
+ },
851
+ children: u.note
852
+ }),
853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
854
+ style: {
855
+ marginLeft: "auto",
856
+ fontSize: 11,
857
+ opacity: .6
858
+ },
859
+ children: [
860
+ u.by === "bootstrap" ? "自动入集" : u.by === "restore" ? "迁移导入" : "手动添加",
861
+ " · ",
862
+ formatTime(u.lastSeenAt)
863
+ ]
864
+ }),
865
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
866
+ type: "button",
867
+ style: {
868
+ ...buttonStyle,
869
+ color: "#e5484d"
870
+ },
871
+ disabled: trustBusy,
872
+ onClick: () => removeTrustedUser(u.userId),
873
+ children: "吊销"
874
+ })
875
+ ]
876
+ }, u.userId))
877
+ }),
878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
879
+ style: {
880
+ display: "flex",
881
+ gap: 8,
882
+ flexWrap: "wrap",
883
+ alignItems: "center"
884
+ },
885
+ children: [
886
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
887
+ type: "text",
888
+ value: newTrustId,
889
+ onChange: (e) => setNewTrustId(e.target.value),
890
+ placeholder: "要添加的微信 userId",
891
+ style: {
892
+ ...inputStyle,
893
+ minWidth: 180
894
+ }
895
+ }),
896
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
897
+ type: "text",
898
+ value: newTrustNote,
899
+ onChange: (e) => setNewTrustNote(e.target.value),
900
+ placeholder: "备注(可选)",
901
+ style: {
902
+ ...inputStyle,
903
+ minWidth: 120
904
+ }
905
+ }),
906
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
907
+ type: "button",
908
+ style: buttonStyle,
909
+ disabled: trustBusy || !newTrustId.trim(),
910
+ onClick: () => void addTrustedUser(),
911
+ children: "添加信任"
912
+ })
913
+ ]
914
+ }),
915
+ trustMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
916
+ role: "status",
917
+ style: {
918
+ color: "#2f9e44",
919
+ marginTop: 6,
920
+ fontSize: 12
921
+ },
922
+ children: trustMessage
923
+ }),
924
+ trustError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
925
+ role: "alert",
926
+ style: {
927
+ color: "#e5484d",
928
+ marginTop: 6,
929
+ fontSize: 12
930
+ },
931
+ children: trustError
932
+ }),
933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
934
+ style: {
935
+ fontSize: 12,
936
+ opacity: .7,
937
+ marginTop: 8
938
+ },
939
+ children: "信任用户与 owner 各自拥有独立会话、独立上下文,互不可见。撤销信任后其新消息将被拒绝,但已有历史保留。 修改信任模式后,新入站消息立即生效,无需重启。"
940
+ })
941
+ ] })]
942
+ }),
443
943
  setupPhase === "starting" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
444
944
  role: "status",
445
945
  style: { marginBottom: 8 },