@xmanrui/dsh-im 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/lib/client.js +709 -285
- package/lib/index.js +114 -110
- package/package.json +1 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +67 -14
- package/plugin-src/client/channels/feishu/api.js +2 -0
- package/plugin-src/client/channels/feishu/index.js +70 -22
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +45 -8
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -8
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +45 -8
- package/plugin-src/client/channels/weixin/api.js +2 -0
- package/plugin-src/client/channels/weixin/index.js +68 -8
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +27 -5
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/client/styles.js +13 -0
- package/plugin-src/client/workspace-editor.js +96 -0
- package/plugin-src/client/workspace-snapshot-fence.js +28 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +34 -11
- package/plugin-src/host/channels/dingtalk/rpc.mjs +17 -2
- package/plugin-src/host/channels/feishu/production.mjs +42 -5
- package/plugin-src/host/channels/feishu/rpc.mjs +17 -2
- package/plugin-src/host/channels/qq/production.mjs +48 -22
- package/plugin-src/host/channels/qq/rpc.mjs +16 -2
- package/plugin-src/host/channels/shared/production.mjs +48 -22
- package/plugin-src/host/channels/shared/rpc.mjs +15 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +25 -0
- package/plugin-src/host/channels/slack/production.mjs +48 -23
- package/plugin-src/host/channels/slack/rpc.mjs +16 -0
- package/plugin-src/host/channels/wecom/production.mjs +49 -23
- package/plugin-src/host/channels/wecom/rpc.mjs +16 -2
- package/plugin-src/host/channels/weixin/production.mjs +31 -11
- package/plugin-src/host/channels/weixin/rpc.mjs +21 -2
- package/plugin-src/host/channels/whatsapp/production.mjs +49 -23
- package/plugin-src/host/channels/whatsapp/rpc.mjs +16 -2
- package/src/channels/dingtalk/dingtalk-bridge.mjs +25 -11
- package/src/channels/dingtalk/dingtalk-controller.mjs +6 -1
- package/src/channels/dingtalk/harness-client.mjs +17 -3
- package/src/channels/dingtalk/state-store.mjs +5 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +39 -16
- package/src/channels/feishu/harness-client.mjs +19 -5
- package/src/channels/feishu/state-store.mjs +5 -0
- package/src/channels/qq/qq-bridge.mjs +28 -15
- package/src/channels/qq/qq-controller.mjs +8 -1
- package/src/channels/qq/state-store.mjs +5 -0
- package/src/channels/shared/bot-workspace-store.mjs +618 -0
- package/src/channels/shared/conversation-state-store.mjs +5 -0
- package/src/channels/shared/text-harness-bridge.mjs +26 -13
- package/src/channels/shared/workspace-command.mjs +115 -0
- package/src/channels/shared/workspace-session.mjs +44 -0
- package/src/channels/wecom/state-store.mjs +5 -0
- package/src/channels/wecom/wecom-bridge.mjs +25 -13
- package/src/channels/wecom/wecom-controller.mjs +8 -1
- package/src/channels/weixin/harness-client.mjs +19 -5
- package/src/channels/weixin/state-store.mjs +5 -0
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +19 -6
- package/src/channels/weixin/weixin-controller.mjs +6 -1
- package/src/channels/whatsapp/whatsapp-controller.mjs +8 -1
package/lib/client.js
CHANGED
|
@@ -40,7 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
name: () => name
|
|
41
41
|
});
|
|
42
42
|
module.exports = __toCommonJS(index_exports);
|
|
43
|
-
var
|
|
43
|
+
var React15 = __toESM(require("react"), 1);
|
|
44
44
|
|
|
45
45
|
// plugin-src/client/channel-logos.js
|
|
46
46
|
var React = __toESM(require("react"), 1);
|
|
@@ -181,7 +181,8 @@ var DINGTALK_ENDPOINTS = Object.freeze({
|
|
|
181
181
|
cancelProvisioning: "provision.cancel",
|
|
182
182
|
bindCredentials: "bot.bind-credentials",
|
|
183
183
|
reconnectBot: "bot.reconnect",
|
|
184
|
-
deleteBot: "bot.delete"
|
|
184
|
+
deleteBot: "bot.delete",
|
|
185
|
+
setWorkspace: "bot.workspace.set"
|
|
185
186
|
});
|
|
186
187
|
var ACCOUNT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
|
|
187
188
|
var SNAPSHOT_STATES = /* @__PURE__ */ new Set(["disconnected", "offline", "provisioning", "connected", "degraded"]);
|
|
@@ -301,6 +302,7 @@ function normalizeBot(value) {
|
|
|
301
302
|
state,
|
|
302
303
|
connected,
|
|
303
304
|
configured: value.configured !== false,
|
|
305
|
+
workspace: optionalString(value.workspace, 4096) ?? "",
|
|
304
306
|
bot: {
|
|
305
307
|
name: optionalString(bot.name, 100) ?? "\u9489\u9489\u673A\u5668\u4EBA",
|
|
306
308
|
clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? "\u5DF2\u5B89\u5168\u4FDD\u5B58"
|
|
@@ -353,7 +355,7 @@ function formatRemaining(milliseconds) {
|
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
// plugin-src/client/channels/dingtalk/index.js
|
|
356
|
-
var
|
|
358
|
+
var React6 = __toESM(require("react"), 1);
|
|
357
359
|
|
|
358
360
|
// plugin-src/client/credential-binding.js
|
|
359
361
|
var React3 = __toESM(require("react"), 1);
|
|
@@ -403,6 +405,19 @@ var EN = Object.freeze({
|
|
|
403
405
|
"\u5DF2\u65AD\u5F00": "Disconnected",
|
|
404
406
|
"\u6D88\u606F\u901A\u9053": "Message channel",
|
|
405
407
|
"\u6700\u8FD1\u68C0\u67E5": "Last checked",
|
|
408
|
+
"\u5F53\u524D\u5DE5\u4F5C\u533A": "Current workspace",
|
|
409
|
+
"\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84": "Absolute workspace path",
|
|
410
|
+
"/\u7EDD\u5BF9\u8DEF\u5F84/\u5230/\u5DE5\u4F5C\u533A": "/absolute/path/to/workspace",
|
|
411
|
+
"\u4FEE\u6539": "Change",
|
|
412
|
+
"\u4FDD\u5B58": "Save",
|
|
413
|
+
"\u4FDD\u5B58\u4E2D\u2026": "Saving\u2026",
|
|
414
|
+
"\u672A\u8BBE\u7F6E": "Not set",
|
|
415
|
+
"\u5DE5\u4F5C\u533A\u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002": "Could not update the workspace. Try again.",
|
|
416
|
+
"\u8BF7\u8F93\u5165\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84\u3002": "Enter an absolute workspace path.",
|
|
417
|
+
"\u5DE5\u4F5C\u533A\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002": "The workspace must be an absolute path.",
|
|
418
|
+
"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u4E0D\u5B58\u5728\u3002": "The workspace path does not exist.",
|
|
419
|
+
"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u4E00\u4E2A\u76EE\u5F55\u3002": "The workspace path must point to a directory.",
|
|
420
|
+
"\u627E\u4E0D\u5230\u8981\u4FEE\u6539\u7684\u673A\u5668\u4EBA\u3002": "The bot could not be found.",
|
|
406
421
|
"\u5C1A\u672A\u68C0\u67E5": "Not checked yet",
|
|
407
422
|
"\u521A\u521A": "Just now",
|
|
408
423
|
"\u68C0\u67E5\u8FDE\u63A5": "Check connection",
|
|
@@ -947,6 +962,132 @@ function CredentialBindingPanel({
|
|
|
947
962
|
);
|
|
948
963
|
}
|
|
949
964
|
|
|
965
|
+
// plugin-src/client/workspace-editor.js
|
|
966
|
+
var React4 = __toESM(require("react"), 1);
|
|
967
|
+
function looksLikeAbsolutePath(value) {
|
|
968
|
+
return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value);
|
|
969
|
+
}
|
|
970
|
+
function WorkspaceEditor({ workspace, disabled = false, onSave }) {
|
|
971
|
+
const [editing, setEditing] = React4.useState(false);
|
|
972
|
+
const [draft, setDraft] = React4.useState(workspace ?? "");
|
|
973
|
+
const [saving, setSaving] = React4.useState(false);
|
|
974
|
+
const [error, setError] = React4.useState(null);
|
|
975
|
+
const inputRef = React4.useRef(null);
|
|
976
|
+
const editButtonRef = React4.useRef(null);
|
|
977
|
+
const restoreFocus = React4.useRef(false);
|
|
978
|
+
React4.useEffect(() => {
|
|
979
|
+
if (!editing) setDraft(workspace ?? "");
|
|
980
|
+
}, [workspace, editing]);
|
|
981
|
+
React4.useEffect(() => {
|
|
982
|
+
if (editing) inputRef.current?.focus();
|
|
983
|
+
else if (restoreFocus.current) {
|
|
984
|
+
restoreFocus.current = false;
|
|
985
|
+
editButtonRef.current?.focus();
|
|
986
|
+
}
|
|
987
|
+
}, [editing]);
|
|
988
|
+
const cancel = () => {
|
|
989
|
+
setDraft(workspace ?? "");
|
|
990
|
+
setError(null);
|
|
991
|
+
restoreFocus.current = true;
|
|
992
|
+
setEditing(false);
|
|
993
|
+
};
|
|
994
|
+
const submit = async (event) => {
|
|
995
|
+
event.preventDefault();
|
|
996
|
+
const value = draft.trim();
|
|
997
|
+
if (!value || saving || disabled) return;
|
|
998
|
+
if (!looksLikeAbsolutePath(value)) {
|
|
999
|
+
setError("\u5DE5\u4F5C\u533A\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
setSaving(true);
|
|
1003
|
+
setError(null);
|
|
1004
|
+
try {
|
|
1005
|
+
await onSave?.(value);
|
|
1006
|
+
restoreFocus.current = true;
|
|
1007
|
+
setEditing(false);
|
|
1008
|
+
} catch (cause) {
|
|
1009
|
+
setError(cause?.message ?? "\u5DE5\u4F5C\u533A\u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
|
|
1010
|
+
} finally {
|
|
1011
|
+
setSaving(false);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
return h2(
|
|
1015
|
+
"div",
|
|
1016
|
+
{ className: "dim-workspace" },
|
|
1017
|
+
h2(
|
|
1018
|
+
"div",
|
|
1019
|
+
{ className: "dim-workspaceHeader" },
|
|
1020
|
+
h2("span", null, "\u5F53\u524D\u5DE5\u4F5C\u533A"),
|
|
1021
|
+
editing ? null : h2("button", {
|
|
1022
|
+
type: "button",
|
|
1023
|
+
ref: editButtonRef,
|
|
1024
|
+
className: "dim-workspaceEdit",
|
|
1025
|
+
onClick: () => {
|
|
1026
|
+
setEditing(true);
|
|
1027
|
+
setError(null);
|
|
1028
|
+
},
|
|
1029
|
+
disabled
|
|
1030
|
+
}, "\u4FEE\u6539")
|
|
1031
|
+
),
|
|
1032
|
+
editing ? h2(
|
|
1033
|
+
"form",
|
|
1034
|
+
{ className: "dim-workspaceForm", onSubmit: submit },
|
|
1035
|
+
h2("input", {
|
|
1036
|
+
ref: inputRef,
|
|
1037
|
+
value: draft,
|
|
1038
|
+
onChange: (event) => setDraft(event.target.value),
|
|
1039
|
+
placeholder: "/\u7EDD\u5BF9\u8DEF\u5F84/\u5230/\u5DE5\u4F5C\u533A",
|
|
1040
|
+
"aria-label": "\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84",
|
|
1041
|
+
autoCapitalize: "none",
|
|
1042
|
+
autoCorrect: "off",
|
|
1043
|
+
spellCheck: false,
|
|
1044
|
+
maxLength: 4096,
|
|
1045
|
+
required: true,
|
|
1046
|
+
disabled: saving || disabled
|
|
1047
|
+
}),
|
|
1048
|
+
h2(
|
|
1049
|
+
"div",
|
|
1050
|
+
{ className: "dim-workspaceActions" },
|
|
1051
|
+
h2("button", {
|
|
1052
|
+
type: "submit",
|
|
1053
|
+
disabled: saving || disabled || !draft.trim()
|
|
1054
|
+
}, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"),
|
|
1055
|
+
h2("button", { type: "button", onClick: cancel, disabled: saving || disabled }, "\u53D6\u6D88")
|
|
1056
|
+
),
|
|
1057
|
+
error ? h2("p", { className: "dim-workspaceError", role: "alert" }, error) : null
|
|
1058
|
+
) : workspace ? React4.createElement("code", {
|
|
1059
|
+
className: "dim-workspacePath",
|
|
1060
|
+
title: workspace
|
|
1061
|
+
}, workspace) : h2("code", { className: "dim-workspacePath" }, "\u672A\u8BBE\u7F6E")
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// plugin-src/client/workspace-snapshot-fence.js
|
|
1066
|
+
var React5 = __toESM(require("react"), 1);
|
|
1067
|
+
function useWorkspaceSnapshotFence() {
|
|
1068
|
+
const state = React5.useRef({ version: 0, pendingMutations: 0 });
|
|
1069
|
+
return React5.useMemo(() => Object.freeze({
|
|
1070
|
+
beginStatus() {
|
|
1071
|
+
return state.current.pendingMutations === 0 ? state.current.version : null;
|
|
1072
|
+
},
|
|
1073
|
+
canCommitStatus(version) {
|
|
1074
|
+
return version !== null && state.current.pendingMutations === 0 && state.current.version === version;
|
|
1075
|
+
},
|
|
1076
|
+
beginMutation() {
|
|
1077
|
+
state.current.pendingMutations += 1;
|
|
1078
|
+
state.current.version += 1;
|
|
1079
|
+
return state.current.version;
|
|
1080
|
+
},
|
|
1081
|
+
canCommitMutation(version) {
|
|
1082
|
+
return state.current.version === version;
|
|
1083
|
+
},
|
|
1084
|
+
endMutation() {
|
|
1085
|
+
state.current.pendingMutations = Math.max(0, state.current.pendingMutations - 1);
|
|
1086
|
+
return state.current.pendingMutations === 0;
|
|
1087
|
+
}
|
|
1088
|
+
}), []);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
950
1091
|
// plugin-src/client/channels/dingtalk/styles.js
|
|
951
1092
|
var DINGTALK_STYLE_ID = "xmanrui-dsh-dingtalk-settings";
|
|
952
1093
|
var CSS = String.raw`
|
|
@@ -1105,7 +1246,7 @@ function DingtalkIcon({ size = 28 }) {
|
|
|
1105
1246
|
d: "M37.05 22.783c-6.758-5.216-14.378-12.128-22.73-19.538-.655-.585-1.242-.354-1.536.42-1.88 4.973-.058 9.386 2.889 11.932s7.368 4.912 10.058 6.155c.105.049.013.203-.093.163-4.953-2.182-8.397-3.765-13.07-7.368-.497-.388-1.01-.242-1.07.521-.384 4.748 2.657 8.483 6.058 9.745 2.1.781 4.398 1.212 6.53 1.474.109.015.084.178-.027.178-2.747.01-6.058-.654-8.935-1.751-.606-.233-.818.25-.722.633.491 2.008 2.974 5.076 6.926 5.73a12 12 0 0 0 2.228.115c.164 0 .208.089.154.217q-2.685 4.6-2.803 4.797c-.091.152-.036.275.156.275h3.543c.164 0 .264.106.18.246l-4.958 8.196c-.191.328.035.565.395.301s15.212-11.133 15.636-11.448c.195-.142.148-.327-.124-.327h-3.18c-.206 0-.252-.14-.111-.28.14-.141 3.602-3.594 4.837-4.888 1.283-1.35 1.938-3.825-.231-5.498"
|
|
1106
1247
|
}));
|
|
1107
1248
|
}
|
|
1108
|
-
var Button =
|
|
1249
|
+
var Button = React6.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
1109
1250
|
return h2("button", {
|
|
1110
1251
|
...props,
|
|
1111
1252
|
ref,
|
|
@@ -1201,13 +1342,13 @@ function EmptyView({ busy, onStart }) {
|
|
|
1201
1342
|
);
|
|
1202
1343
|
}
|
|
1203
1344
|
function QrPanel({ provision, now, busy, onRefresh, onCancel }) {
|
|
1204
|
-
const [imageFailed, setImageFailed] =
|
|
1345
|
+
const [imageFailed, setImageFailed] = React6.useState(false);
|
|
1205
1346
|
const source = safeQrSource(provision.qrCodeDataUrl);
|
|
1206
1347
|
const remaining = Math.max(0, provision.expiresAt - now);
|
|
1207
1348
|
const expired = remaining === 0 || provision.status === "expired";
|
|
1208
1349
|
const duration = Math.max(1, provision.durationMs ?? 10 * 6e4);
|
|
1209
1350
|
const progress = Math.round(Math.min(1, remaining / duration) * 100);
|
|
1210
|
-
|
|
1351
|
+
React6.useEffect(() => setImageFailed(false), [source]);
|
|
1211
1352
|
return h2(
|
|
1212
1353
|
"div",
|
|
1213
1354
|
{ className: "ddt-card dim-surfaceCard" },
|
|
@@ -1324,8 +1465,8 @@ function checkedTime(value) {
|
|
|
1324
1465
|
}
|
|
1325
1466
|
}
|
|
1326
1467
|
function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|
1327
|
-
const cancelRef =
|
|
1328
|
-
|
|
1468
|
+
const cancelRef = React6.useRef(null);
|
|
1469
|
+
React6.useEffect(() => cancelRef.current?.focus(), []);
|
|
1329
1470
|
return h2(
|
|
1330
1471
|
"div",
|
|
1331
1472
|
{
|
|
@@ -1355,6 +1496,7 @@ function AccountCard({
|
|
|
1355
1496
|
busy,
|
|
1356
1497
|
removing,
|
|
1357
1498
|
onReconnect,
|
|
1499
|
+
onWorkspaceSave,
|
|
1358
1500
|
onRequestRemove,
|
|
1359
1501
|
onConfirmRemove,
|
|
1360
1502
|
onCancelRemove
|
|
@@ -1406,6 +1548,11 @@ function AccountCard({
|
|
|
1406
1548
|
h2("dd", null, checkedTime(account.health.lastCheckedAt))
|
|
1407
1549
|
)
|
|
1408
1550
|
),
|
|
1551
|
+
h2(WorkspaceEditor, {
|
|
1552
|
+
workspace: account.workspace,
|
|
1553
|
+
disabled: Boolean(busy),
|
|
1554
|
+
onSave: onWorkspaceSave
|
|
1555
|
+
}),
|
|
1409
1556
|
h2(
|
|
1410
1557
|
"div",
|
|
1411
1558
|
{ className: "ddt-accountFooter dim-cardFooter" },
|
|
@@ -1451,6 +1598,7 @@ function AccountList(props) {
|
|
|
1451
1598
|
busy: props.busyByBot[account.botId],
|
|
1452
1599
|
removing: props.removeTarget === account.botId,
|
|
1453
1600
|
onReconnect: () => props.onReconnect(account),
|
|
1601
|
+
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
|
1454
1602
|
onRequestRemove: () => props.onRequestRemove(account),
|
|
1455
1603
|
onConfirmRemove: () => props.onConfirmRemove(account),
|
|
1456
1604
|
onCancelRemove: props.onCancelRemove
|
|
@@ -1460,27 +1608,28 @@ function AccountList(props) {
|
|
|
1460
1608
|
}
|
|
1461
1609
|
var EMPTY_TOTALS = Object.freeze({ configured: 0, connected: 0 });
|
|
1462
1610
|
function DingtalkSettingsTab({ rpcCall }) {
|
|
1463
|
-
const [model, setModel] =
|
|
1611
|
+
const [model, setModel] = React6.useState({
|
|
1464
1612
|
phase: "loading",
|
|
1465
1613
|
bots: [],
|
|
1466
1614
|
totals: EMPTY_TOTALS,
|
|
1467
1615
|
revision: 0,
|
|
1468
1616
|
error: null
|
|
1469
1617
|
});
|
|
1470
|
-
const [provision, setProvision] =
|
|
1471
|
-
const [busy, setBusy] =
|
|
1472
|
-
const [busyByBot, setBusyByBot] =
|
|
1473
|
-
const [removeTarget, setRemoveTarget] =
|
|
1474
|
-
const [credentialOpen, setCredentialOpen] =
|
|
1475
|
-
const [credentialError, setCredentialError] =
|
|
1476
|
-
const [notice, setNotice] =
|
|
1477
|
-
const [now, setNow] =
|
|
1478
|
-
const addButtonRef =
|
|
1479
|
-
const mountedRef =
|
|
1480
|
-
const statusRequestRef =
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1618
|
+
const [provision, setProvision] = React6.useState(null);
|
|
1619
|
+
const [busy, setBusy] = React6.useState(false);
|
|
1620
|
+
const [busyByBot, setBusyByBot] = React6.useState({});
|
|
1621
|
+
const [removeTarget, setRemoveTarget] = React6.useState(null);
|
|
1622
|
+
const [credentialOpen, setCredentialOpen] = React6.useState(false);
|
|
1623
|
+
const [credentialError, setCredentialError] = React6.useState(null);
|
|
1624
|
+
const [notice, setNotice] = React6.useState("");
|
|
1625
|
+
const [now, setNow] = React6.useState(() => Date.now());
|
|
1626
|
+
const addButtonRef = React6.useRef(null);
|
|
1627
|
+
const mountedRef = React6.useRef(true);
|
|
1628
|
+
const statusRequestRef = React6.useRef(0);
|
|
1629
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
1630
|
+
const noticeFrameRef = React6.useRef(null);
|
|
1631
|
+
const focusFrameRef = React6.useRef(null);
|
|
1632
|
+
React6.useEffect(() => {
|
|
1484
1633
|
mountedRef.current = true;
|
|
1485
1634
|
return () => {
|
|
1486
1635
|
mountedRef.current = false;
|
|
@@ -1495,8 +1644,8 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1495
1644
|
}
|
|
1496
1645
|
};
|
|
1497
1646
|
}, []);
|
|
1498
|
-
|
|
1499
|
-
const announce =
|
|
1647
|
+
React6.useEffect(() => installDingtalkStyles(), []);
|
|
1648
|
+
const announce = React6.useCallback((message) => {
|
|
1500
1649
|
if (!mountedRef.current) return;
|
|
1501
1650
|
if (noticeFrameRef.current !== null) {
|
|
1502
1651
|
window.cancelAnimationFrame(noticeFrameRef.current);
|
|
@@ -1510,7 +1659,7 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1510
1659
|
});
|
|
1511
1660
|
}
|
|
1512
1661
|
}, []);
|
|
1513
|
-
const focusAddButton =
|
|
1662
|
+
const focusAddButton = React6.useCallback(() => {
|
|
1514
1663
|
if (!mountedRef.current) return;
|
|
1515
1664
|
if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current);
|
|
1516
1665
|
focusFrameRef.current = window.requestAnimationFrame(() => {
|
|
@@ -1518,19 +1667,21 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1518
1667
|
if (mountedRef.current) addButtonRef.current?.focus();
|
|
1519
1668
|
});
|
|
1520
1669
|
}, []);
|
|
1521
|
-
const invoke =
|
|
1670
|
+
const invoke = React6.useCallback(async (endpoint, payload = {}, signal) => {
|
|
1522
1671
|
if (typeof rpcCall !== "function") throw new TypeError("\u9489\u9489\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
|
|
1523
1672
|
return unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
1524
1673
|
}, [rpcCall]);
|
|
1525
|
-
const loadStatus =
|
|
1674
|
+
const loadStatus = React6.useCallback(async ({
|
|
1526
1675
|
signal,
|
|
1527
1676
|
silent = false,
|
|
1528
1677
|
restoreProvisioning = false
|
|
1529
1678
|
} = {}) => {
|
|
1530
1679
|
if (!mountedRef.current || signal?.aborted) return void 0;
|
|
1680
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
1681
|
+
if (workspaceVersion === null) return void 0;
|
|
1531
1682
|
const requestId = statusRequestRef.current + 1;
|
|
1532
1683
|
statusRequestRef.current = requestId;
|
|
1533
|
-
const canCommit = () => mountedRef.current && !signal?.aborted && statusRequestRef.current === requestId;
|
|
1684
|
+
const canCommit = () => mountedRef.current && !signal?.aborted && statusRequestRef.current === requestId && workspaceFence.canCommitStatus(workspaceVersion);
|
|
1534
1685
|
if (!silent && canCommit()) {
|
|
1535
1686
|
setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
1536
1687
|
}
|
|
@@ -1561,13 +1712,13 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1561
1712
|
}));
|
|
1562
1713
|
return void 0;
|
|
1563
1714
|
}
|
|
1564
|
-
}, [invoke]);
|
|
1565
|
-
|
|
1715
|
+
}, [invoke, workspaceFence]);
|
|
1716
|
+
React6.useEffect(() => {
|
|
1566
1717
|
const controller = new AbortController();
|
|
1567
1718
|
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
|
|
1568
1719
|
return () => controller.abort();
|
|
1569
1720
|
}, [loadStatus]);
|
|
1570
|
-
|
|
1721
|
+
React6.useEffect(() => {
|
|
1571
1722
|
if (model.phase !== "ready") return void 0;
|
|
1572
1723
|
const controller = new AbortController();
|
|
1573
1724
|
let running = false;
|
|
@@ -1586,14 +1737,14 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1586
1737
|
window.clearInterval(timer);
|
|
1587
1738
|
};
|
|
1588
1739
|
}, [loadStatus, model.phase]);
|
|
1589
|
-
|
|
1740
|
+
React6.useEffect(() => {
|
|
1590
1741
|
if (!provision || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0;
|
|
1591
1742
|
const timer = window.setInterval(() => {
|
|
1592
1743
|
if (mountedRef.current) setNow(Date.now());
|
|
1593
1744
|
}, 1e3);
|
|
1594
1745
|
return () => window.clearInterval(timer);
|
|
1595
1746
|
}, [provision?.attemptId, provision?.status]);
|
|
1596
|
-
const startProvisioning =
|
|
1747
|
+
const startProvisioning = React6.useCallback(async ({ replace = false } = {}) => {
|
|
1597
1748
|
if (!mountedRef.current) return;
|
|
1598
1749
|
setCredentialOpen(false);
|
|
1599
1750
|
setCredentialError(null);
|
|
@@ -1631,8 +1782,9 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1631
1782
|
if (mountedRef.current) setBusy(false);
|
|
1632
1783
|
}
|
|
1633
1784
|
}, [announce, invoke, provision?.attemptId]);
|
|
1634
|
-
const bindCredentials =
|
|
1785
|
+
const bindCredentials = React6.useCallback(async ({ identity, secret }) => {
|
|
1635
1786
|
if (!mountedRef.current) return;
|
|
1787
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
1636
1788
|
setBusy(true);
|
|
1637
1789
|
setCredentialError(null);
|
|
1638
1790
|
try {
|
|
@@ -1641,22 +1793,26 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1641
1793
|
{ clientId: identity, clientSecret: secret }
|
|
1642
1794
|
));
|
|
1643
1795
|
if (!mountedRef.current) return;
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1796
|
+
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
1797
|
+
setModel({
|
|
1798
|
+
phase: "ready",
|
|
1799
|
+
bots: snapshot.bots,
|
|
1800
|
+
totals: snapshot.totals,
|
|
1801
|
+
revision: snapshot.revision,
|
|
1802
|
+
error: null
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1651
1805
|
setCredentialOpen(false);
|
|
1652
1806
|
announce("\u9489\u9489\u673A\u5668\u4EBA\u51ED\u636E\u5DF2\u7ED1\u5B9A\u3002");
|
|
1653
1807
|
} catch (error) {
|
|
1654
1808
|
if (mountedRef.current) setCredentialError(presentError(error));
|
|
1655
1809
|
} finally {
|
|
1810
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1811
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
1656
1812
|
if (mountedRef.current) setBusy(false);
|
|
1657
1813
|
}
|
|
1658
|
-
}, [announce, invoke]);
|
|
1659
|
-
const cancelProvisioning =
|
|
1814
|
+
}, [announce, invoke, loadStatus, workspaceFence]);
|
|
1815
|
+
const cancelProvisioning = React6.useCallback(async () => {
|
|
1660
1816
|
if (!mountedRef.current) return;
|
|
1661
1817
|
setBusy(true);
|
|
1662
1818
|
try {
|
|
@@ -1674,7 +1830,7 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1674
1830
|
if (mountedRef.current) setBusy(false);
|
|
1675
1831
|
}
|
|
1676
1832
|
}, [announce, focusAddButton, invoke, provision?.attemptId, provision?.status]);
|
|
1677
|
-
|
|
1833
|
+
React6.useEffect(() => {
|
|
1678
1834
|
const attemptId = provision?.attemptId;
|
|
1679
1835
|
if (!attemptId || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0;
|
|
1680
1836
|
const controller = new AbortController();
|
|
@@ -1733,7 +1889,7 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1733
1889
|
timer = null;
|
|
1734
1890
|
};
|
|
1735
1891
|
}, [announce, invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
|
|
1736
|
-
const setBotBusy =
|
|
1892
|
+
const setBotBusy = React6.useCallback((botId, operation) => {
|
|
1737
1893
|
if (!mountedRef.current) return;
|
|
1738
1894
|
setBusyByBot((current) => {
|
|
1739
1895
|
const next = { ...current };
|
|
@@ -1742,14 +1898,22 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1742
1898
|
return next;
|
|
1743
1899
|
});
|
|
1744
1900
|
}, []);
|
|
1745
|
-
const runBotAction =
|
|
1901
|
+
const runBotAction = React6.useCallback(async ({ account, operation, endpoint, payload, success }) => {
|
|
1746
1902
|
if (!mountedRef.current) return void 0;
|
|
1903
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
1747
1904
|
setBotBusy(account.botId, operation);
|
|
1748
1905
|
try {
|
|
1749
|
-
await invoke(endpoint, payload);
|
|
1750
|
-
if (!mountedRef.current) return void 0;
|
|
1751
|
-
const snapshot = await loadStatus({ silent: true, restoreProvisioning: false });
|
|
1906
|
+
const snapshot = normalizeSnapshot(await invoke(endpoint, payload));
|
|
1752
1907
|
if (!mountedRef.current) return void 0;
|
|
1908
|
+
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
1909
|
+
setModel({
|
|
1910
|
+
phase: "ready",
|
|
1911
|
+
bots: snapshot.bots,
|
|
1912
|
+
totals: snapshot.totals,
|
|
1913
|
+
revision: snapshot.revision,
|
|
1914
|
+
error: null
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1753
1917
|
announce(typeof success === "function" ? success(snapshot) : success);
|
|
1754
1918
|
return snapshot;
|
|
1755
1919
|
} catch (error) {
|
|
@@ -1757,17 +1921,42 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1757
1921
|
announce(`\u64CD\u4F5C\u5931\u8D25\uFF1A${presentError(error).message}`);
|
|
1758
1922
|
return void 0;
|
|
1759
1923
|
} finally {
|
|
1924
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1925
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true, restoreProvisioning: false });
|
|
1760
1926
|
if (mountedRef.current) setBotBusy(account.botId, null);
|
|
1761
1927
|
}
|
|
1762
|
-
}, [announce, invoke, loadStatus, setBotBusy]);
|
|
1763
|
-
const reconnect =
|
|
1928
|
+
}, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
1929
|
+
const reconnect = React6.useCallback((account) => runBotAction({
|
|
1764
1930
|
account,
|
|
1765
1931
|
operation: "reconnect",
|
|
1766
1932
|
endpoint: DINGTALK_ENDPOINTS.reconnectBot,
|
|
1767
1933
|
payload: { botId: account.botId },
|
|
1768
1934
|
success: (snapshot) => snapshot?.bots.find((bot) => bot.botId === account.botId)?.connected ? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002" : "\u9489\u9489\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002"
|
|
1769
1935
|
}), [runBotAction]);
|
|
1770
|
-
const
|
|
1936
|
+
const saveWorkspace = React6.useCallback(async (account, workspace) => {
|
|
1937
|
+
const workspaceVersion = workspaceFence.beginMutation();
|
|
1938
|
+
setBotBusy(account.botId, "workspace");
|
|
1939
|
+
try {
|
|
1940
|
+
const snapshot = normalizeSnapshot(await invoke(
|
|
1941
|
+
DINGTALK_ENDPOINTS.setWorkspace,
|
|
1942
|
+
{ botId: account.botId, workspace }
|
|
1943
|
+
));
|
|
1944
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(workspaceVersion)) {
|
|
1945
|
+
setModel({
|
|
1946
|
+
phase: "ready",
|
|
1947
|
+
bots: snapshot.bots,
|
|
1948
|
+
totals: snapshot.totals,
|
|
1949
|
+
revision: snapshot.revision,
|
|
1950
|
+
error: null
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
} finally {
|
|
1954
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1955
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
1956
|
+
if (mountedRef.current) setBotBusy(account.botId, null);
|
|
1957
|
+
}
|
|
1958
|
+
}, [invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
1959
|
+
const remove = React6.useCallback(async (account) => {
|
|
1771
1960
|
const snapshot = await runBotAction({
|
|
1772
1961
|
account,
|
|
1773
1962
|
operation: "delete",
|
|
@@ -1849,7 +2038,7 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1849
2038
|
h2(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
|
|
1850
2039
|
)
|
|
1851
2040
|
) : h2(
|
|
1852
|
-
|
|
2041
|
+
React6.Fragment,
|
|
1853
2042
|
null,
|
|
1854
2043
|
credentialView,
|
|
1855
2044
|
provisionView,
|
|
@@ -1859,6 +2048,7 @@ function DingtalkSettingsTab({ rpcCall }) {
|
|
|
1859
2048
|
busyByBot,
|
|
1860
2049
|
removeTarget,
|
|
1861
2050
|
onReconnect: (account) => void reconnect(account),
|
|
2051
|
+
onWorkspaceSave: saveWorkspace,
|
|
1862
2052
|
onRequestRemove: (account) => setRemoveTarget(account.botId),
|
|
1863
2053
|
onConfirmRemove: (account) => void remove(account),
|
|
1864
2054
|
onCancelRemove: () => setRemoveTarget(null)
|
|
@@ -1888,7 +2078,8 @@ var TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|
|
1888
2078
|
status: "connection.status",
|
|
1889
2079
|
bindCredentials: "bot.bind-credentials",
|
|
1890
2080
|
reconnectBot: "bot.reconnect",
|
|
1891
|
-
deleteBot: "bot.delete"
|
|
2081
|
+
deleteBot: "bot.delete",
|
|
2082
|
+
setWorkspace: "bot.workspace.set"
|
|
1892
2083
|
});
|
|
1893
2084
|
function createTokenChannelApi(channel4, connectionSummary) {
|
|
1894
2085
|
const unwrapRpcResult10 = (result) => {
|
|
@@ -1910,6 +2101,7 @@ function createTokenChannelApi(channel4, connectionSummary) {
|
|
|
1910
2101
|
botId: id(value.botId),
|
|
1911
2102
|
connected,
|
|
1912
2103
|
state: connected ? "connected" : state,
|
|
2104
|
+
workspace: text(value.workspace, "", 4096),
|
|
1913
2105
|
bot: {
|
|
1914
2106
|
name: text(value.bot?.name, `${channel4}\u673A\u5668\u4EBA`, 100),
|
|
1915
2107
|
username: text(value.bot?.username, "", 100),
|
|
@@ -1956,8 +2148,8 @@ var normalizeSnapshot2 = api.normalizeSnapshot;
|
|
|
1956
2148
|
var presentError2 = api.presentError;
|
|
1957
2149
|
|
|
1958
2150
|
// plugin-src/client/channels/shared/token-channel.js
|
|
1959
|
-
var
|
|
1960
|
-
var Button3 =
|
|
2151
|
+
var React7 = __toESM(require("react"), 1);
|
|
2152
|
+
var Button3 = React7.forwardRef(function Button4({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
1961
2153
|
return h2("button", {
|
|
1962
2154
|
...props,
|
|
1963
2155
|
ref,
|
|
@@ -2000,7 +2192,7 @@ function createTokenChannelSettings(definition) {
|
|
|
2000
2192
|
credentialNoun = "Bot Token",
|
|
2001
2193
|
emptyActionLabel = "\u586B\u5199 Bot Token"
|
|
2002
2194
|
} = definition;
|
|
2003
|
-
function AccountCard5({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
|
2195
|
+
function AccountCard5({ account, busy, removing, onReconnect, onWorkspaceSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
|
2004
2196
|
const state = busy === "reconnect" ? "connecting" : account.state;
|
|
2005
2197
|
const tone = account.connected ? "success" : state === "error" ? "error" : "warning";
|
|
2006
2198
|
const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
|
|
@@ -2053,6 +2245,11 @@ function createTokenChannelSettings(definition) {
|
|
|
2053
2245
|
h2("dd", null, checkedTime2(account.health.lastCheckedAt))
|
|
2054
2246
|
)
|
|
2055
2247
|
),
|
|
2248
|
+
h2(WorkspaceEditor, {
|
|
2249
|
+
workspace: account.workspace,
|
|
2250
|
+
disabled: Boolean(busy),
|
|
2251
|
+
onSave: onWorkspaceSave
|
|
2252
|
+
}),
|
|
2056
2253
|
h2(
|
|
2057
2254
|
"div",
|
|
2058
2255
|
{ className: "ddt-accountFooter dim-cardFooter" },
|
|
@@ -2093,19 +2290,20 @@ function createTokenChannelSettings(definition) {
|
|
|
2093
2290
|
);
|
|
2094
2291
|
}
|
|
2095
2292
|
function SettingsTab({ rpcCall }) {
|
|
2096
|
-
const [model, setModel] =
|
|
2293
|
+
const [model, setModel] = React7.useState({
|
|
2097
2294
|
phase: "loading",
|
|
2098
2295
|
bots: [],
|
|
2099
2296
|
totals: { configured: 0, connected: 0 },
|
|
2100
2297
|
error: null
|
|
2101
2298
|
});
|
|
2102
|
-
const [credentialOpen, setCredentialOpen] =
|
|
2103
|
-
const [credentialError, setCredentialError] =
|
|
2104
|
-
const [busy, setBusy] =
|
|
2105
|
-
const [busyByBot, setBusyByBot] =
|
|
2106
|
-
const [removeTarget, setRemoveTarget] =
|
|
2107
|
-
const mounted =
|
|
2108
|
-
|
|
2299
|
+
const [credentialOpen, setCredentialOpen] = React7.useState(false);
|
|
2300
|
+
const [credentialError, setCredentialError] = React7.useState(null);
|
|
2301
|
+
const [busy, setBusy] = React7.useState(false);
|
|
2302
|
+
const [busyByBot, setBusyByBot] = React7.useState({});
|
|
2303
|
+
const [removeTarget, setRemoveTarget] = React7.useState(null);
|
|
2304
|
+
const mounted = React7.useRef(true);
|
|
2305
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
2306
|
+
React7.useEffect(() => {
|
|
2109
2307
|
const disposeDingtalk = installDingtalkStyles();
|
|
2110
2308
|
const disposeChannel = installStyles();
|
|
2111
2309
|
mounted.current = true;
|
|
@@ -2115,18 +2313,20 @@ function createTokenChannelSettings(definition) {
|
|
|
2115
2313
|
disposeDingtalk();
|
|
2116
2314
|
};
|
|
2117
2315
|
}, []);
|
|
2118
|
-
const invoke =
|
|
2316
|
+
const invoke = React7.useCallback(async (endpoint, payload = {}, signal) => {
|
|
2119
2317
|
if (typeof rpcCall !== "function") throw new TypeError(`${channel4} \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5`);
|
|
2120
2318
|
return api4.unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
2121
2319
|
}, [rpcCall]);
|
|
2122
|
-
const loadStatus =
|
|
2320
|
+
const loadStatus = React7.useCallback(async ({ signal, silent = false } = {}) => {
|
|
2321
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
2322
|
+
if (workspaceVersion === null) return;
|
|
2123
2323
|
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
2124
2324
|
try {
|
|
2125
2325
|
const snapshot = api4.normalizeSnapshot(await invoke(endpoints.status, {}, signal));
|
|
2126
|
-
if (!mounted.current || signal?.aborted) return;
|
|
2326
|
+
if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return;
|
|
2127
2327
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
2128
2328
|
} catch (error) {
|
|
2129
|
-
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
|
|
2329
|
+
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted && workspaceFence.canCommitStatus(workspaceVersion)) {
|
|
2130
2330
|
setModel((current) => ({
|
|
2131
2331
|
...current,
|
|
2132
2332
|
phase: silent ? current.phase : "error",
|
|
@@ -2134,13 +2334,13 @@ function createTokenChannelSettings(definition) {
|
|
|
2134
2334
|
}));
|
|
2135
2335
|
}
|
|
2136
2336
|
}
|
|
2137
|
-
}, [invoke]);
|
|
2138
|
-
|
|
2337
|
+
}, [invoke, workspaceFence]);
|
|
2338
|
+
React7.useEffect(() => {
|
|
2139
2339
|
const controller = new AbortController();
|
|
2140
2340
|
void loadStatus({ signal: controller.signal });
|
|
2141
2341
|
return () => controller.abort();
|
|
2142
2342
|
}, [loadStatus]);
|
|
2143
|
-
|
|
2343
|
+
React7.useEffect(() => {
|
|
2144
2344
|
if (model.phase !== "ready") return void 0;
|
|
2145
2345
|
const controller = new AbortController();
|
|
2146
2346
|
const timer = window.setInterval(
|
|
@@ -2152,7 +2352,8 @@ function createTokenChannelSettings(definition) {
|
|
|
2152
2352
|
window.clearInterval(timer);
|
|
2153
2353
|
};
|
|
2154
2354
|
}, [loadStatus, model.phase]);
|
|
2155
|
-
const bindCredentials =
|
|
2355
|
+
const bindCredentials = React7.useCallback(async (values) => {
|
|
2356
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
2156
2357
|
setBusy(true);
|
|
2157
2358
|
setCredentialError(null);
|
|
2158
2359
|
try {
|
|
@@ -2161,29 +2362,36 @@ function createTokenChannelSettings(definition) {
|
|
|
2161
2362
|
credentialPayload(values)
|
|
2162
2363
|
));
|
|
2163
2364
|
if (!mounted.current) return;
|
|
2164
|
-
|
|
2365
|
+
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
2366
|
+
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
2367
|
+
}
|
|
2165
2368
|
setCredentialOpen(false);
|
|
2166
2369
|
} catch (error) {
|
|
2167
2370
|
if (mounted.current) setCredentialError(api4.presentError(error));
|
|
2168
2371
|
} finally {
|
|
2372
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
2373
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
2169
2374
|
if (mounted.current) setBusy(false);
|
|
2170
2375
|
}
|
|
2171
|
-
}, [invoke]);
|
|
2172
|
-
const botAction =
|
|
2376
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
2377
|
+
const botAction = React7.useCallback(async (account, operation, endpoint, payload) => {
|
|
2378
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
2173
2379
|
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
|
2174
2380
|
try {
|
|
2175
2381
|
const snapshot = api4.normalizeSnapshot(await invoke(endpoint, payload));
|
|
2176
|
-
if (mounted.current) {
|
|
2382
|
+
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
2177
2383
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
2178
2384
|
}
|
|
2179
2385
|
} finally {
|
|
2386
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
2387
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
2180
2388
|
if (mounted.current) setBusyByBot((current) => {
|
|
2181
2389
|
const next = { ...current };
|
|
2182
2390
|
delete next[account.botId];
|
|
2183
2391
|
return next;
|
|
2184
2392
|
});
|
|
2185
2393
|
}
|
|
2186
|
-
}, [invoke]);
|
|
2394
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
2187
2395
|
const botList = model.bots.length > 0 ? h2(
|
|
2188
2396
|
"section",
|
|
2189
2397
|
{ className: "dim-listSection" },
|
|
@@ -2202,6 +2410,12 @@ function createTokenChannelSettings(definition) {
|
|
|
2202
2410
|
endpoints.reconnectBot,
|
|
2203
2411
|
{ botId: account.botId }
|
|
2204
2412
|
),
|
|
2413
|
+
onWorkspaceSave: (workspace) => botAction(
|
|
2414
|
+
account,
|
|
2415
|
+
"workspace",
|
|
2416
|
+
endpoints.setWorkspace,
|
|
2417
|
+
{ botId: account.botId, workspace }
|
|
2418
|
+
),
|
|
2205
2419
|
onRequestRemove: () => setRemoveTarget(account.botId),
|
|
2206
2420
|
onCancelRemove: () => setRemoveTarget(null),
|
|
2207
2421
|
onConfirmRemove: async () => {
|
|
@@ -2261,7 +2475,7 @@ function createTokenChannelSettings(definition) {
|
|
|
2261
2475
|
h2(Button3, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
|
|
2262
2476
|
)
|
|
2263
2477
|
) : h2(
|
|
2264
|
-
|
|
2478
|
+
React7.Fragment,
|
|
2265
2479
|
null,
|
|
2266
2480
|
credentialOpen ? CredentialPanel ? h2(CredentialPanel, {
|
|
2267
2481
|
busy,
|
|
@@ -2362,7 +2576,7 @@ var DiscordSettingsTab = channel.SettingsTab;
|
|
|
2362
2576
|
var DiscordAccountCard = channel.AccountCard;
|
|
2363
2577
|
|
|
2364
2578
|
// plugin-src/client/channels/feishu/index.js
|
|
2365
|
-
var
|
|
2579
|
+
var React9 = __toESM(require("react"), 1);
|
|
2366
2580
|
|
|
2367
2581
|
// plugin-src/client/channels/feishu/api.js
|
|
2368
2582
|
var FEISHU_RPC_CHANNEL = "/feishu";
|
|
@@ -2375,6 +2589,7 @@ var FEISHU_ENDPOINTS = Object.freeze({
|
|
|
2375
2589
|
reconnectBot: "bot.reconnect",
|
|
2376
2590
|
disconnectBot: "bot.disconnect",
|
|
2377
2591
|
deleteBot: "bot.delete",
|
|
2592
|
+
setWorkspace: "bot.workspace.set",
|
|
2378
2593
|
// Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
|
|
2379
2594
|
testConnection: "connection.test",
|
|
2380
2595
|
disconnect: "connection.disconnect"
|
|
@@ -2491,6 +2706,7 @@ function normalizeBotConnection(value, fallbackBotId) {
|
|
|
2491
2706
|
state: authoritativeState(value.state, connected),
|
|
2492
2707
|
connected,
|
|
2493
2708
|
configured: value.configured !== false,
|
|
2709
|
+
workspace: optionalString2(value.workspace)?.slice(0, 4096) ?? "",
|
|
2494
2710
|
bot: normalizeBot2(value.bot),
|
|
2495
2711
|
health: normalizeHealth(value.health, connected),
|
|
2496
2712
|
error: normalizeError2(value.error)
|
|
@@ -2580,7 +2796,7 @@ function formatRemaining2(milliseconds) {
|
|
|
2580
2796
|
}
|
|
2581
2797
|
|
|
2582
2798
|
// plugin-src/client/lifecycle.js
|
|
2583
|
-
var
|
|
2799
|
+
var React8 = __toESM(require("react"), 1);
|
|
2584
2800
|
function createPollScheduler({ setTimeoutFn, clearTimeoutFn }) {
|
|
2585
2801
|
let disposed = false;
|
|
2586
2802
|
let timer;
|
|
@@ -2642,8 +2858,8 @@ function createAnimationFrameScheduler({ requestFrame, cancelFrame }) {
|
|
|
2642
2858
|
};
|
|
2643
2859
|
}
|
|
2644
2860
|
function useAnimationFrameScheduler() {
|
|
2645
|
-
const schedulerRef =
|
|
2646
|
-
|
|
2861
|
+
const schedulerRef = React8.useRef(null);
|
|
2862
|
+
React8.useEffect(() => {
|
|
2647
2863
|
const scheduler = createAnimationFrameScheduler({
|
|
2648
2864
|
requestFrame: (callback) => window.requestAnimationFrame(callback),
|
|
2649
2865
|
cancelFrame: (frame) => window.cancelAnimationFrame(frame)
|
|
@@ -2654,7 +2870,7 @@ function useAnimationFrameScheduler() {
|
|
|
2654
2870
|
if (schedulerRef.current === scheduler) schedulerRef.current = null;
|
|
2655
2871
|
};
|
|
2656
2872
|
}, []);
|
|
2657
|
-
return
|
|
2873
|
+
return React8.useCallback(
|
|
2658
2874
|
(callback, key) => schedulerRef.current?.schedule(callback, key) ?? false,
|
|
2659
2875
|
[]
|
|
2660
2876
|
);
|
|
@@ -3222,7 +3438,7 @@ function QrIcon({ size = 58 }) {
|
|
|
3222
3438
|
fill: "currentColor"
|
|
3223
3439
|
}));
|
|
3224
3440
|
}
|
|
3225
|
-
var Button5 =
|
|
3441
|
+
var Button5 = React9.forwardRef(function Button6({ children, kind = "secondary", size, icon, className = "", ...props }, ref) {
|
|
3226
3442
|
return h2("button", {
|
|
3227
3443
|
...props,
|
|
3228
3444
|
ref,
|
|
@@ -3334,13 +3550,13 @@ function safeQrSource2(value) {
|
|
|
3334
3550
|
return /^data:image\/(?:png|webp|svg\+xml)(?:;charset=[^;,]+)?;base64,/i.test(value) ? value : void 0;
|
|
3335
3551
|
}
|
|
3336
3552
|
function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
3337
|
-
const [imageFailed, setImageFailed] =
|
|
3553
|
+
const [imageFailed, setImageFailed] = React9.useState(false);
|
|
3338
3554
|
const qrSource = safeQrSource2(provision.qrCodeDataUrl);
|
|
3339
3555
|
const href = safeVerificationHref(provision.verificationUrl);
|
|
3340
3556
|
const remaining = Math.max(0, provision.expiresAt - now);
|
|
3341
3557
|
const expired = provision.expired === true || remaining === 0;
|
|
3342
3558
|
const progress = Math.min(1, remaining / Math.max(1, provision.durationMs ?? remaining));
|
|
3343
|
-
|
|
3559
|
+
React9.useEffect(() => setImageFailed(false), [qrSource]);
|
|
3344
3560
|
return h2(
|
|
3345
3561
|
"div",
|
|
3346
3562
|
{ className: "bxf-card bxf-provisionCard dim-surfaceCard" },
|
|
@@ -3490,11 +3706,11 @@ function formatCheckedTime(timestamp7) {
|
|
|
3490
3706
|
}
|
|
3491
3707
|
}
|
|
3492
3708
|
function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
|
|
3493
|
-
const cancelRef =
|
|
3709
|
+
const cancelRef = React9.useRef(null);
|
|
3494
3710
|
const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3495
3711
|
const titleId = `bxf-remove-title-${idPart}`;
|
|
3496
3712
|
const descriptionId = `bxf-remove-description-${idPart}`;
|
|
3497
|
-
|
|
3713
|
+
React9.useEffect(() => cancelRef.current?.focus(), []);
|
|
3498
3714
|
return h2(
|
|
3499
3715
|
"div",
|
|
3500
3716
|
{
|
|
@@ -3533,6 +3749,7 @@ function BotCard({
|
|
|
3533
3749
|
actionError,
|
|
3534
3750
|
removing,
|
|
3535
3751
|
onReconnect,
|
|
3752
|
+
onWorkspaceSave,
|
|
3536
3753
|
onRequestRemove,
|
|
3537
3754
|
onConfirmRemove,
|
|
3538
3755
|
onCancelRemove,
|
|
@@ -3597,6 +3814,11 @@ function BotCard({
|
|
|
3597
3814
|
h2("dd", null, formatCheckedTime(health.lastCheckedAt))
|
|
3598
3815
|
)
|
|
3599
3816
|
),
|
|
3817
|
+
h2(WorkspaceEditor, {
|
|
3818
|
+
workspace: connection.workspace,
|
|
3819
|
+
disabled: Boolean(busy),
|
|
3820
|
+
onSave: onWorkspaceSave
|
|
3821
|
+
}),
|
|
3600
3822
|
h2(
|
|
3601
3823
|
"div",
|
|
3602
3824
|
{ className: "bxf-connectedFooter dim-cardFooter" },
|
|
@@ -3655,6 +3877,7 @@ function BotList(props) {
|
|
|
3655
3877
|
actionError: props.errorsByBot[bot.botId],
|
|
3656
3878
|
removing: props.removeTargetId === bot.botId,
|
|
3657
3879
|
onReconnect: () => props.onReconnect(bot),
|
|
3880
|
+
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
|
3658
3881
|
onRequestRemove: () => props.onRequestRemove(bot),
|
|
3659
3882
|
onConfirmRemove: () => props.onConfirmRemove(bot),
|
|
3660
3883
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -3715,7 +3938,7 @@ function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = fal
|
|
|
3715
3938
|
};
|
|
3716
3939
|
}
|
|
3717
3940
|
function FeishuSettingsTab({ rpcCall }) {
|
|
3718
|
-
const [model, setModel] =
|
|
3941
|
+
const [model, setModel] = React9.useState({
|
|
3719
3942
|
phase: "loading",
|
|
3720
3943
|
revision: 0,
|
|
3721
3944
|
bots: [],
|
|
@@ -3724,31 +3947,39 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3724
3947
|
pageError: null,
|
|
3725
3948
|
statusError: null
|
|
3726
3949
|
});
|
|
3727
|
-
const [pageBusy, setPageBusy] =
|
|
3728
|
-
const [provisionBusy, setProvisionBusy] =
|
|
3729
|
-
const [credentialOpen, setCredentialOpen] =
|
|
3730
|
-
const [credentialBusy, setCredentialBusy] =
|
|
3731
|
-
const [credentialError, setCredentialError] =
|
|
3732
|
-
const [busyByBot, setBusyByBot] =
|
|
3733
|
-
const [errorsByBot, setErrorsByBot] =
|
|
3734
|
-
const [removeTargetId, setRemoveTargetId] =
|
|
3735
|
-
const [announcement, setAnnouncement] =
|
|
3736
|
-
const [now, setNow] =
|
|
3737
|
-
const [focusBotId, setFocusBotId] =
|
|
3738
|
-
const cardRefs =
|
|
3739
|
-
const removeButtonRefs =
|
|
3740
|
-
const addButtonRef =
|
|
3950
|
+
const [pageBusy, setPageBusy] = React9.useState(false);
|
|
3951
|
+
const [provisionBusy, setProvisionBusy] = React9.useState(false);
|
|
3952
|
+
const [credentialOpen, setCredentialOpen] = React9.useState(false);
|
|
3953
|
+
const [credentialBusy, setCredentialBusy] = React9.useState(false);
|
|
3954
|
+
const [credentialError, setCredentialError] = React9.useState(null);
|
|
3955
|
+
const [busyByBot, setBusyByBot] = React9.useState({});
|
|
3956
|
+
const [errorsByBot, setErrorsByBot] = React9.useState({});
|
|
3957
|
+
const [removeTargetId, setRemoveTargetId] = React9.useState(null);
|
|
3958
|
+
const [announcement, setAnnouncement] = React9.useState("");
|
|
3959
|
+
const [now, setNow] = React9.useState(() => Date.now());
|
|
3960
|
+
const [focusBotId, setFocusBotId] = React9.useState(null);
|
|
3961
|
+
const cardRefs = React9.useRef(/* @__PURE__ */ new Map());
|
|
3962
|
+
const removeButtonRefs = React9.useRef(/* @__PURE__ */ new Map());
|
|
3963
|
+
const addButtonRef = React9.useRef(null);
|
|
3964
|
+
const mountedRef = React9.useRef(true);
|
|
3965
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
3741
3966
|
const scheduleAnimationFrame = useAnimationFrameScheduler();
|
|
3742
|
-
|
|
3967
|
+
React9.useEffect(() => {
|
|
3968
|
+
mountedRef.current = true;
|
|
3969
|
+
return () => {
|
|
3970
|
+
mountedRef.current = false;
|
|
3971
|
+
};
|
|
3972
|
+
}, []);
|
|
3973
|
+
const announce = React9.useCallback((message) => {
|
|
3743
3974
|
setAnnouncement("");
|
|
3744
3975
|
scheduleAnimationFrame(() => {
|
|
3745
3976
|
if (message) setAnnouncement(message);
|
|
3746
3977
|
}, "announcement");
|
|
3747
3978
|
}, [scheduleAnimationFrame]);
|
|
3748
|
-
const invoke =
|
|
3979
|
+
const invoke = React9.useCallback(async (endpoint, payload = {}, signal) => {
|
|
3749
3980
|
return unwrapRpcResult3(await rpcCall(endpoint, payload, signal));
|
|
3750
3981
|
}, [rpcCall]);
|
|
3751
|
-
const mergeSnapshot =
|
|
3982
|
+
const mergeSnapshot = React9.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
|
|
3752
3983
|
const now2 = Date.now();
|
|
3753
3984
|
setModel((current) => mergeFeishuSnapshotState(
|
|
3754
3985
|
current,
|
|
@@ -3756,28 +3987,30 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3756
3987
|
{ restoreProvisioning, now: now2 }
|
|
3757
3988
|
));
|
|
3758
3989
|
}, []);
|
|
3759
|
-
const loadStatus =
|
|
3990
|
+
const loadStatus = React9.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => {
|
|
3991
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
3992
|
+
if (workspaceVersion === null || !mountedRef.current) return void 0;
|
|
3760
3993
|
if (!silent) setPageBusy(true);
|
|
3761
3994
|
try {
|
|
3762
3995
|
const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.status, {}, signal));
|
|
3763
|
-
if (signal?.aborted) return void 0;
|
|
3996
|
+
if (signal?.aborted || !mountedRef.current || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
3764
3997
|
mergeSnapshot(snapshot, { restoreProvisioning });
|
|
3765
3998
|
return snapshot;
|
|
3766
3999
|
} catch (error) {
|
|
3767
|
-
if (signal?.aborted || error?.name === "AbortError") return void 0;
|
|
4000
|
+
if (signal?.aborted || error?.name === "AbortError" || !mountedRef.current || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
3768
4001
|
const presented = presentError3(error);
|
|
3769
4002
|
setModel((current) => current.phase === "loading" || !silent ? { ...current, phase: "error", pageError: presented } : { ...current, statusError: presented });
|
|
3770
4003
|
return void 0;
|
|
3771
4004
|
} finally {
|
|
3772
|
-
if (!silent && !signal?.aborted) setPageBusy(false);
|
|
4005
|
+
if (!silent && !signal?.aborted && mountedRef.current) setPageBusy(false);
|
|
3773
4006
|
}
|
|
3774
|
-
}, [invoke, mergeSnapshot]);
|
|
3775
|
-
|
|
4007
|
+
}, [invoke, mergeSnapshot, workspaceFence]);
|
|
4008
|
+
React9.useEffect(() => {
|
|
3776
4009
|
const controller = new AbortController();
|
|
3777
4010
|
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
|
|
3778
4011
|
return () => controller.abort();
|
|
3779
4012
|
}, [loadStatus]);
|
|
3780
|
-
|
|
4013
|
+
React9.useEffect(() => {
|
|
3781
4014
|
if (model.phase !== "ready") return void 0;
|
|
3782
4015
|
const controller = new AbortController();
|
|
3783
4016
|
let inFlight = false;
|
|
@@ -3796,7 +4029,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3796
4029
|
window.clearInterval(timer);
|
|
3797
4030
|
};
|
|
3798
4031
|
}, [loadStatus, model.phase]);
|
|
3799
|
-
|
|
4032
|
+
React9.useEffect(() => {
|
|
3800
4033
|
if (!focusBotId) return;
|
|
3801
4034
|
const node = cardRefs.current.get(focusBotId);
|
|
3802
4035
|
if (!node) return;
|
|
@@ -3804,7 +4037,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3804
4037
|
node.focus({ preventScroll: true });
|
|
3805
4038
|
setFocusBotId(null);
|
|
3806
4039
|
}, [focusBotId, model.bots]);
|
|
3807
|
-
const startProvisioning =
|
|
4040
|
+
const startProvisioning = React9.useCallback(async ({ replace = false } = {}) => {
|
|
3808
4041
|
setCredentialOpen(false);
|
|
3809
4042
|
setCredentialError(null);
|
|
3810
4043
|
setProvisionBusy(true);
|
|
@@ -3844,7 +4077,8 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3844
4077
|
setProvisionBusy(false);
|
|
3845
4078
|
}
|
|
3846
4079
|
}, [announce, invoke, model.provisioning?.attemptId]);
|
|
3847
|
-
const bindCredentials =
|
|
4080
|
+
const bindCredentials = React9.useCallback(async ({ identity, secret }) => {
|
|
4081
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
3848
4082
|
setCredentialBusy(true);
|
|
3849
4083
|
setCredentialError(null);
|
|
3850
4084
|
try {
|
|
@@ -3852,16 +4086,20 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3852
4086
|
FEISHU_ENDPOINTS.bindCredentials,
|
|
3853
4087
|
{ appId: identity, appSecret: secret }
|
|
3854
4088
|
));
|
|
3855
|
-
|
|
4089
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
4090
|
+
mergeSnapshot(snapshot);
|
|
4091
|
+
}
|
|
3856
4092
|
setCredentialOpen(false);
|
|
3857
4093
|
announce("\u98DE\u4E66\u673A\u5668\u4EBA\u51ED\u636E\u5DF2\u7ED1\u5B9A\u3002");
|
|
3858
4094
|
} catch (error) {
|
|
3859
4095
|
setCredentialError(presentError3(error));
|
|
3860
4096
|
} finally {
|
|
4097
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4098
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
3861
4099
|
setCredentialBusy(false);
|
|
3862
4100
|
}
|
|
3863
|
-
}, [announce, invoke, mergeSnapshot]);
|
|
3864
|
-
const cancelProvisioning =
|
|
4101
|
+
}, [announce, invoke, loadStatus, mergeSnapshot, workspaceFence]);
|
|
4102
|
+
const cancelProvisioning = React9.useCallback(async () => {
|
|
3865
4103
|
const attemptId = model.provisioning?.attemptId;
|
|
3866
4104
|
setProvisionBusy(true);
|
|
3867
4105
|
try {
|
|
@@ -3883,7 +4121,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3883
4121
|
const countdownPhase = model.provisioning?.phase;
|
|
3884
4122
|
const countdownExpiresAt = model.provisioning?.expiresAt;
|
|
3885
4123
|
const countdownExpired = model.provisioning?.expired;
|
|
3886
|
-
|
|
4124
|
+
React9.useEffect(() => {
|
|
3887
4125
|
if (!countdownAttemptId || countdownPhase !== "qr" || countdownExpired) return void 0;
|
|
3888
4126
|
const tick = () => {
|
|
3889
4127
|
const timestamp7 = Date.now();
|
|
@@ -3896,7 +4134,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3896
4134
|
const timer = window.setInterval(tick, 1e3);
|
|
3897
4135
|
return () => window.clearInterval(timer);
|
|
3898
4136
|
}, [countdownAttemptId, countdownPhase, countdownExpiresAt, countdownExpired]);
|
|
3899
|
-
|
|
4137
|
+
React9.useEffect(() => {
|
|
3900
4138
|
const provision2 = model.provisioning;
|
|
3901
4139
|
if (!provision2 || !["qr", "connecting"].includes(provision2.phase) || !provision2.attemptId || provision2.expired) return void 0;
|
|
3902
4140
|
const controller = new AbortController();
|
|
@@ -3960,7 +4198,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3960
4198
|
window.clearTimeout(timer);
|
|
3961
4199
|
};
|
|
3962
4200
|
}, [announce, invoke, loadStatus, model.provisioning]);
|
|
3963
|
-
const setBotBusy =
|
|
4201
|
+
const setBotBusy = React9.useCallback((botId, value) => {
|
|
3964
4202
|
setBusyByBot((current) => {
|
|
3965
4203
|
const next = { ...current };
|
|
3966
4204
|
if (value) next[botId] = value;
|
|
@@ -3968,7 +4206,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3968
4206
|
return next;
|
|
3969
4207
|
});
|
|
3970
4208
|
}, []);
|
|
3971
|
-
const setBotError =
|
|
4209
|
+
const setBotError = React9.useCallback((botId, error) => {
|
|
3972
4210
|
setErrorsByBot((current) => {
|
|
3973
4211
|
const next = { ...current };
|
|
3974
4212
|
if (error) next[botId] = presentError3(error);
|
|
@@ -3976,13 +4214,16 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3976
4214
|
return next;
|
|
3977
4215
|
});
|
|
3978
4216
|
}, []);
|
|
3979
|
-
const reconnectOneBot =
|
|
4217
|
+
const reconnectOneBot = React9.useCallback(async (connection) => {
|
|
3980
4218
|
const { botId, bot } = connection;
|
|
4219
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
3981
4220
|
setBotBusy(botId, "reconnect");
|
|
3982
4221
|
setBotError(botId, null);
|
|
3983
4222
|
try {
|
|
3984
4223
|
const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId }));
|
|
3985
|
-
|
|
4224
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
4225
|
+
mergeSnapshot(snapshot);
|
|
4226
|
+
}
|
|
3986
4227
|
const refreshed = snapshot.bots.find((item) => item.botId === botId);
|
|
3987
4228
|
if (!refreshed?.connected) {
|
|
3988
4229
|
const error = new Error(
|
|
@@ -3996,45 +4237,63 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
3996
4237
|
setBotError(botId, error);
|
|
3997
4238
|
announce(`${bot.name}\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u673A\u5668\u4EBA\u72B6\u6001\u3002`);
|
|
3998
4239
|
} finally {
|
|
4240
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4241
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
3999
4242
|
setBotBusy(botId, null);
|
|
4000
4243
|
}
|
|
4001
|
-
}, [announce, invoke, mergeSnapshot, setBotBusy, setBotError]);
|
|
4002
|
-
const
|
|
4244
|
+
}, [announce, invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
4245
|
+
const saveWorkspace = React9.useCallback(async (connection, workspace) => {
|
|
4246
|
+
const { botId } = connection;
|
|
4247
|
+
const workspaceVersion = workspaceFence.beginMutation();
|
|
4248
|
+
setBotBusy(botId, "workspace");
|
|
4249
|
+
setBotError(botId, null);
|
|
4250
|
+
try {
|
|
4251
|
+
const snapshot = normalizeBotsSnapshot(await invoke(
|
|
4252
|
+
FEISHU_ENDPOINTS.setWorkspace,
|
|
4253
|
+
{ botId, workspace }
|
|
4254
|
+
));
|
|
4255
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(workspaceVersion)) {
|
|
4256
|
+
mergeSnapshot(snapshot);
|
|
4257
|
+
}
|
|
4258
|
+
} finally {
|
|
4259
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4260
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
4261
|
+
if (mountedRef.current) setBotBusy(botId, null);
|
|
4262
|
+
}
|
|
4263
|
+
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
4264
|
+
const requestRemove = React9.useCallback((connection) => {
|
|
4003
4265
|
setRemoveTargetId(connection.botId);
|
|
4004
4266
|
}, []);
|
|
4005
|
-
const cancelRemove =
|
|
4267
|
+
const cancelRemove = React9.useCallback(() => {
|
|
4006
4268
|
const botId = removeTargetId;
|
|
4007
4269
|
setRemoveTargetId(null);
|
|
4008
4270
|
scheduleAnimationFrame(() => removeButtonRefs.current.get(botId)?.focus(), "focus");
|
|
4009
4271
|
}, [removeTargetId, scheduleAnimationFrame]);
|
|
4010
|
-
const confirmRemove =
|
|
4272
|
+
const confirmRemove = React9.useCallback(async (connection) => {
|
|
4011
4273
|
const { botId, bot } = connection;
|
|
4274
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
4012
4275
|
setBotBusy(botId, "delete");
|
|
4013
4276
|
setBotError(botId, null);
|
|
4014
4277
|
try {
|
|
4015
|
-
await invoke(
|
|
4278
|
+
const snapshot = normalizeBotsSnapshot(await invoke(
|
|
4279
|
+
FEISHU_ENDPOINTS.deleteBot,
|
|
4280
|
+
{ botId, confirm: true }
|
|
4281
|
+
));
|
|
4016
4282
|
setRemoveTargetId(null);
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
...current,
|
|
4021
|
-
bots,
|
|
4022
|
-
totals: {
|
|
4023
|
-
configured: bots.length,
|
|
4024
|
-
connected: bots.filter((item) => item.connected).length
|
|
4025
|
-
}
|
|
4026
|
-
};
|
|
4027
|
-
});
|
|
4283
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
4284
|
+
mergeSnapshot(snapshot);
|
|
4285
|
+
}
|
|
4028
4286
|
announce(`${bot.name}\u5DF2\u4ECE\u6B64 DeepSeek Harness \u79FB\u9664\uFF1B\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u5E94\u7528\u672A\u88AB\u5220\u9664\u3002`);
|
|
4029
|
-
await loadStatus({ silent: true });
|
|
4030
4287
|
scheduleAnimationFrame(() => addButtonRef.current?.focus(), "focus");
|
|
4031
4288
|
} catch (error) {
|
|
4032
4289
|
setBotError(botId, error);
|
|
4033
4290
|
announce(`${bot.name}\u79FB\u9664\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002`);
|
|
4034
4291
|
} finally {
|
|
4292
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4293
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
4035
4294
|
setBotBusy(botId, null);
|
|
4036
4295
|
}
|
|
4037
|
-
}, [announce, invoke, loadStatus, scheduleAnimationFrame, setBotBusy, setBotError]);
|
|
4296
|
+
}, [announce, invoke, loadStatus, mergeSnapshot, scheduleAnimationFrame, setBotBusy, setBotError, workspaceFence]);
|
|
4038
4297
|
const provision = model.provisioning;
|
|
4039
4298
|
let provisionContent = null;
|
|
4040
4299
|
if (provision?.phase === "creating") {
|
|
@@ -4075,11 +4334,11 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
4075
4334
|
setCredentialError(null);
|
|
4076
4335
|
}
|
|
4077
4336
|
}) : null;
|
|
4078
|
-
const setCardRef =
|
|
4337
|
+
const setCardRef = React9.useCallback((botId, node) => {
|
|
4079
4338
|
if (node) cardRefs.current.set(botId, node);
|
|
4080
4339
|
else cardRefs.current.delete(botId);
|
|
4081
4340
|
}, []);
|
|
4082
|
-
const setRemoveButtonRef =
|
|
4341
|
+
const setRemoveButtonRef = React9.useCallback((botId, node) => {
|
|
4083
4342
|
if (node) removeButtonRefs.current.set(botId, node);
|
|
4084
4343
|
else removeButtonRefs.current.delete(botId);
|
|
4085
4344
|
}, []);
|
|
@@ -4116,7 +4375,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
4116
4375
|
onRetry: () => void loadStatus(),
|
|
4117
4376
|
busy: pageBusy
|
|
4118
4377
|
}) : h2(
|
|
4119
|
-
|
|
4378
|
+
React9.Fragment,
|
|
4120
4379
|
null,
|
|
4121
4380
|
credentialContent,
|
|
4122
4381
|
provisionContent,
|
|
@@ -4127,6 +4386,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
|
|
4127
4386
|
errorsByBot,
|
|
4128
4387
|
removeTargetId,
|
|
4129
4388
|
onReconnect: (bot) => void reconnectOneBot(bot),
|
|
4389
|
+
onWorkspaceSave: saveWorkspace,
|
|
4130
4390
|
onRequestRemove: requestRemove,
|
|
4131
4391
|
onConfirmRemove: (bot) => void confirmRemove(bot),
|
|
4132
4392
|
onCancelRemove: cancelRemove,
|
|
@@ -4146,7 +4406,8 @@ var QQ_ENDPOINTS = Object.freeze({
|
|
|
4146
4406
|
cancelProvisioning: "provision.cancel",
|
|
4147
4407
|
bindCredentials: "bot.bind-credentials",
|
|
4148
4408
|
reconnectBot: "bot.reconnect",
|
|
4149
|
-
deleteBot: "bot.delete"
|
|
4409
|
+
deleteBot: "bot.delete",
|
|
4410
|
+
setWorkspace: "bot.workspace.set"
|
|
4150
4411
|
});
|
|
4151
4412
|
var PROVISION_STATES2 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
|
|
4152
4413
|
var ACCOUNT_STATES3 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
|
|
@@ -4208,6 +4469,7 @@ function normalizeBot3(value) {
|
|
|
4208
4469
|
botId: id2(value.botId),
|
|
4209
4470
|
connected,
|
|
4210
4471
|
state: connected ? "connected" : state,
|
|
4472
|
+
workspace: text2(value.workspace, "", 4096),
|
|
4211
4473
|
bot: {
|
|
4212
4474
|
name: text2(value.bot?.name, "QQ\u673A\u5668\u4EBA", 100),
|
|
4213
4475
|
appIdMasked: text2(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
|
|
@@ -4245,7 +4507,7 @@ function formatRemaining3(milliseconds) {
|
|
|
4245
4507
|
}
|
|
4246
4508
|
|
|
4247
4509
|
// plugin-src/client/channels/qq/index.js
|
|
4248
|
-
var
|
|
4510
|
+
var React10 = __toESM(require("react"), 1);
|
|
4249
4511
|
|
|
4250
4512
|
// plugin-src/client/channels/qq/styles.js
|
|
4251
4513
|
var QQ_STYLE_ID = "xmanrui-dsh-im-qq-settings";
|
|
@@ -4270,7 +4532,7 @@ function installQqStyles() {
|
|
|
4270
4532
|
|
|
4271
4533
|
// plugin-src/client/channels/qq/index.js
|
|
4272
4534
|
var ACTIVE_STATES = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
|
|
4273
|
-
var Button7 =
|
|
4535
|
+
var Button7 = React10.forwardRef(function Button8({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
4274
4536
|
return h2("button", {
|
|
4275
4537
|
...props,
|
|
4276
4538
|
ref,
|
|
@@ -4477,7 +4739,16 @@ function RemoveConfirmation3({ account, busy, onConfirm, onCancel }) {
|
|
|
4477
4739
|
)
|
|
4478
4740
|
);
|
|
4479
4741
|
}
|
|
4480
|
-
function AccountCard2({
|
|
4742
|
+
function AccountCard2({
|
|
4743
|
+
account,
|
|
4744
|
+
busy,
|
|
4745
|
+
removing,
|
|
4746
|
+
onReconnect,
|
|
4747
|
+
onWorkspaceSave,
|
|
4748
|
+
onRequestRemove,
|
|
4749
|
+
onConfirmRemove,
|
|
4750
|
+
onCancelRemove
|
|
4751
|
+
}) {
|
|
4481
4752
|
const tone = account.connected ? "success" : account.state === "error" ? "error" : "warning";
|
|
4482
4753
|
const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : account.state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
|
|
4483
4754
|
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
|
|
@@ -4514,6 +4785,11 @@ function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, o
|
|
|
4514
4785
|
h2("div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6D88\u606F\u901A\u9053"), h2("dd", null, account.connected ? "WebSocket \u957F\u8FDE\u63A5" : "\u79BB\u7EBF")),
|
|
4515
4786
|
h2("div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h2("dd", null, checkedTime3(account.health.lastCheckedAt)))
|
|
4516
4787
|
),
|
|
4788
|
+
h2(WorkspaceEditor, {
|
|
4789
|
+
workspace: account.workspace,
|
|
4790
|
+
disabled: Boolean(busy),
|
|
4791
|
+
onSave: onWorkspaceSave
|
|
4792
|
+
}),
|
|
4517
4793
|
h2(
|
|
4518
4794
|
"div",
|
|
4519
4795
|
{ className: "ddt-accountFooter dim-cardFooter" },
|
|
@@ -4535,17 +4811,18 @@ function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, o
|
|
|
4535
4811
|
);
|
|
4536
4812
|
}
|
|
4537
4813
|
function QqSettingsTab({ rpcCall }) {
|
|
4538
|
-
const [model, setModel] =
|
|
4539
|
-
const [provision, setProvision] =
|
|
4540
|
-
const [busy, setBusy] =
|
|
4541
|
-
const [busyByBot, setBusyByBot] =
|
|
4542
|
-
const [removeTarget, setRemoveTarget] =
|
|
4543
|
-
const [credentialOpen, setCredentialOpen] =
|
|
4544
|
-
const [credentialError, setCredentialError] =
|
|
4545
|
-
const [now, setNow] =
|
|
4546
|
-
const mounted =
|
|
4547
|
-
const
|
|
4548
|
-
|
|
4814
|
+
const [model, setModel] = React10.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
|
|
4815
|
+
const [provision, setProvision] = React10.useState(null);
|
|
4816
|
+
const [busy, setBusy] = React10.useState(false);
|
|
4817
|
+
const [busyByBot, setBusyByBot] = React10.useState({});
|
|
4818
|
+
const [removeTarget, setRemoveTarget] = React10.useState(null);
|
|
4819
|
+
const [credentialOpen, setCredentialOpen] = React10.useState(false);
|
|
4820
|
+
const [credentialError, setCredentialError] = React10.useState(null);
|
|
4821
|
+
const [now, setNow] = React10.useState(Date.now());
|
|
4822
|
+
const mounted = React10.useRef(true);
|
|
4823
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
4824
|
+
const addButtonRef = React10.useRef(null);
|
|
4825
|
+
React10.useEffect(() => {
|
|
4549
4826
|
const disposeDingtalk = installDingtalkStyles();
|
|
4550
4827
|
const disposeQq = installQqStyles();
|
|
4551
4828
|
mounted.current = true;
|
|
@@ -4555,15 +4832,17 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4555
4832
|
disposeDingtalk();
|
|
4556
4833
|
};
|
|
4557
4834
|
}, []);
|
|
4558
|
-
const invoke =
|
|
4835
|
+
const invoke = React10.useCallback(async (endpoint, payload = {}, signal) => {
|
|
4559
4836
|
if (typeof rpcCall !== "function") throw new TypeError("QQ \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
|
|
4560
4837
|
return unwrapRpcResult4(await rpcCall(endpoint, payload, signal));
|
|
4561
4838
|
}, [rpcCall]);
|
|
4562
|
-
const loadStatus =
|
|
4839
|
+
const loadStatus = React10.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
4840
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
4841
|
+
if (workspaceVersion === null) return void 0;
|
|
4563
4842
|
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
4564
4843
|
try {
|
|
4565
4844
|
const snapshot = normalizeSnapshot3(await invoke(QQ_ENDPOINTS.status, {}, signal));
|
|
4566
|
-
if (!mounted.current || signal?.aborted) return void 0;
|
|
4845
|
+
if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
4567
4846
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
4568
4847
|
if (restore && snapshot.provisioning) setProvision({
|
|
4569
4848
|
...snapshot.provisioning,
|
|
@@ -4571,18 +4850,18 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4571
4850
|
});
|
|
4572
4851
|
return snapshot;
|
|
4573
4852
|
} catch (error) {
|
|
4574
|
-
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
|
|
4853
|
+
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted && workspaceFence.canCommitStatus(workspaceVersion)) {
|
|
4575
4854
|
setModel((current) => ({ ...current, phase: silent ? current.phase : "error", error: presentError4(error) }));
|
|
4576
4855
|
}
|
|
4577
4856
|
return void 0;
|
|
4578
4857
|
}
|
|
4579
|
-
}, [invoke]);
|
|
4580
|
-
|
|
4858
|
+
}, [invoke, workspaceFence]);
|
|
4859
|
+
React10.useEffect(() => {
|
|
4581
4860
|
const controller = new AbortController();
|
|
4582
4861
|
void loadStatus({ signal: controller.signal, restore: true });
|
|
4583
4862
|
return () => controller.abort();
|
|
4584
4863
|
}, [loadStatus]);
|
|
4585
|
-
|
|
4864
|
+
React10.useEffect(() => {
|
|
4586
4865
|
if (model.phase !== "ready") return void 0;
|
|
4587
4866
|
const controller = new AbortController();
|
|
4588
4867
|
const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
|
|
@@ -4591,12 +4870,12 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4591
4870
|
window.clearInterval(timer);
|
|
4592
4871
|
};
|
|
4593
4872
|
}, [loadStatus, model.phase]);
|
|
4594
|
-
|
|
4873
|
+
React10.useEffect(() => {
|
|
4595
4874
|
if (!provision || !ACTIVE_STATES.has(provision.status)) return void 0;
|
|
4596
4875
|
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
|
|
4597
4876
|
return () => window.clearInterval(timer);
|
|
4598
4877
|
}, [provision?.attemptId, provision?.status]);
|
|
4599
|
-
const startProvisioning =
|
|
4878
|
+
const startProvisioning = React10.useCallback(async (replace = false) => {
|
|
4600
4879
|
setCredentialOpen(false);
|
|
4601
4880
|
setCredentialError(null);
|
|
4602
4881
|
setBusy(true);
|
|
@@ -4614,7 +4893,8 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4614
4893
|
if (mounted.current) setBusy(false);
|
|
4615
4894
|
}
|
|
4616
4895
|
}, [invoke, provision?.attemptId]);
|
|
4617
|
-
const bindCredentials =
|
|
4896
|
+
const bindCredentials = React10.useCallback(async ({ identity, secret }) => {
|
|
4897
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
4618
4898
|
setBusy(true);
|
|
4619
4899
|
setCredentialError(null);
|
|
4620
4900
|
try {
|
|
@@ -4623,15 +4903,19 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4623
4903
|
{ appId: identity, appSecret: secret }
|
|
4624
4904
|
));
|
|
4625
4905
|
if (!mounted.current) return;
|
|
4626
|
-
|
|
4906
|
+
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
4907
|
+
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
4908
|
+
}
|
|
4627
4909
|
setCredentialOpen(false);
|
|
4628
4910
|
} catch (error) {
|
|
4629
4911
|
if (mounted.current) setCredentialError(presentError4(error));
|
|
4630
4912
|
} finally {
|
|
4913
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4914
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
4631
4915
|
if (mounted.current) setBusy(false);
|
|
4632
4916
|
}
|
|
4633
|
-
}, [invoke]);
|
|
4634
|
-
const closeProvision =
|
|
4917
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
4918
|
+
const closeProvision = React10.useCallback(async () => {
|
|
4635
4919
|
setBusy(true);
|
|
4636
4920
|
try {
|
|
4637
4921
|
if (provision?.attemptId && ACTIVE_STATES.has(provision.status)) {
|
|
@@ -4642,7 +4926,7 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4642
4926
|
if (mounted.current) setBusy(false);
|
|
4643
4927
|
}
|
|
4644
4928
|
}, [invoke, provision?.attemptId, provision?.status]);
|
|
4645
|
-
|
|
4929
|
+
React10.useEffect(() => {
|
|
4646
4930
|
const attemptId = provision?.attemptId;
|
|
4647
4931
|
if (!attemptId || !ACTIVE_STATES.has(provision.status)) return void 0;
|
|
4648
4932
|
const controller = new AbortController();
|
|
@@ -4672,20 +4956,25 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4672
4956
|
window.clearTimeout(timer);
|
|
4673
4957
|
};
|
|
4674
4958
|
}, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
|
|
4675
|
-
const botAction =
|
|
4959
|
+
const botAction = React10.useCallback(async (account, operation, endpoint, payload) => {
|
|
4960
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
4676
4961
|
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
|
4677
4962
|
try {
|
|
4678
4963
|
const snapshot = normalizeSnapshot3(await invoke(endpoint, payload));
|
|
4679
|
-
if (mounted.current
|
|
4964
|
+
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
4965
|
+
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
4966
|
+
}
|
|
4680
4967
|
return snapshot;
|
|
4681
4968
|
} finally {
|
|
4969
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
4970
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
4682
4971
|
if (mounted.current) setBusyByBot((current) => {
|
|
4683
4972
|
const next = { ...current };
|
|
4684
4973
|
delete next[account.botId];
|
|
4685
4974
|
return next;
|
|
4686
4975
|
});
|
|
4687
4976
|
}
|
|
4688
|
-
}, [invoke]);
|
|
4977
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
4689
4978
|
let provisionView = null;
|
|
4690
4979
|
if (provision?.status === "starting") provisionView = h2("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h2("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7 QQ \u4E8C\u7EF4\u7801\u2026");
|
|
4691
4980
|
else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h2(QrPanel2, {
|
|
@@ -4714,6 +5003,12 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4714
5003
|
busy: busyByBot[account.botId],
|
|
4715
5004
|
removing: removeTarget === account.botId,
|
|
4716
5005
|
onReconnect: () => void botAction(account, "reconnect", QQ_ENDPOINTS.reconnectBot, { botId: account.botId }),
|
|
5006
|
+
onWorkspaceSave: (workspace) => botAction(
|
|
5007
|
+
account,
|
|
5008
|
+
"workspace",
|
|
5009
|
+
QQ_ENDPOINTS.setWorkspace,
|
|
5010
|
+
{ botId: account.botId, workspace }
|
|
5011
|
+
),
|
|
4717
5012
|
onRequestRemove: () => setRemoveTarget(account.botId),
|
|
4718
5013
|
onCancelRemove: () => setRemoveTarget(null),
|
|
4719
5014
|
onConfirmRemove: async () => {
|
|
@@ -4752,7 +5047,7 @@ function QqSettingsTab({ rpcCall }) {
|
|
|
4752
5047
|
addButtonRef
|
|
4753
5048
|
}),
|
|
4754
5049
|
model.phase === "loading" ? h2(LoadingView3) : model.phase === "error" ? h2("div", { className: "ddt-card dim-surfaceCard" }, h2("div", { className: "ddt-inlineError dim-inlineError" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6 QQ \u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message), h2(Button7, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h2(
|
|
4755
|
-
|
|
5050
|
+
React10.Fragment,
|
|
4756
5051
|
null,
|
|
4757
5052
|
credentialView,
|
|
4758
5053
|
provisionView,
|
|
@@ -4771,7 +5066,7 @@ var normalizeSnapshot4 = api2.normalizeSnapshot;
|
|
|
4771
5066
|
var presentError5 = api2.presentError;
|
|
4772
5067
|
|
|
4773
5068
|
// plugin-src/client/channels/slack/index.js
|
|
4774
|
-
var
|
|
5069
|
+
var React11 = __toESM(require("react"), 1);
|
|
4775
5070
|
|
|
4776
5071
|
// src/channels/slack/manifest.mjs
|
|
4777
5072
|
var SLACK_APP_MANIFEST_YAML = `_metadata:
|
|
@@ -4843,10 +5138,10 @@ function installSlackStyles() {
|
|
|
4843
5138
|
|
|
4844
5139
|
// plugin-src/client/channels/slack/index.js
|
|
4845
5140
|
function SlackCredentialPanel({ busy, error, onSubmit, onCancel }) {
|
|
4846
|
-
const [botToken, setBotToken] =
|
|
4847
|
-
const [appToken, setAppToken] =
|
|
4848
|
-
const [copied, setCopied] =
|
|
4849
|
-
const headingId =
|
|
5141
|
+
const [botToken, setBotToken] = React11.useState("");
|
|
5142
|
+
const [appToken, setAppToken] = React11.useState("");
|
|
5143
|
+
const [copied, setCopied] = React11.useState(false);
|
|
5144
|
+
const headingId = React11.useId();
|
|
4850
5145
|
const copyManifest = async () => {
|
|
4851
5146
|
try {
|
|
4852
5147
|
await navigator.clipboard.writeText(SLACK_APP_MANIFEST_YAML);
|
|
@@ -5039,7 +5334,8 @@ var WECOM_ENDPOINTS = Object.freeze({
|
|
|
5039
5334
|
cancelProvisioning: "provision.cancel",
|
|
5040
5335
|
bindCredentials: "bot.bind-credentials",
|
|
5041
5336
|
reconnectBot: "bot.reconnect",
|
|
5042
|
-
deleteBot: "bot.delete"
|
|
5337
|
+
deleteBot: "bot.delete",
|
|
5338
|
+
setWorkspace: "bot.workspace.set"
|
|
5043
5339
|
});
|
|
5044
5340
|
var PROVISION_STATES3 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
|
|
5045
5341
|
var ACCOUNT_STATES4 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
|
|
@@ -5101,6 +5397,7 @@ function normalizeBot4(value) {
|
|
|
5101
5397
|
botId: id3(value.botId),
|
|
5102
5398
|
connected,
|
|
5103
5399
|
state: connected ? "connected" : state,
|
|
5400
|
+
workspace: text3(value.workspace, "", 4096),
|
|
5104
5401
|
bot: {
|
|
5105
5402
|
name: text3(value.bot?.name, "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA", 100),
|
|
5106
5403
|
appIdMasked: text3(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
|
|
@@ -5138,7 +5435,7 @@ function formatRemaining4(milliseconds) {
|
|
|
5138
5435
|
}
|
|
5139
5436
|
|
|
5140
5437
|
// plugin-src/client/channels/wecom/index.js
|
|
5141
|
-
var
|
|
5438
|
+
var React12 = __toESM(require("react"), 1);
|
|
5142
5439
|
|
|
5143
5440
|
// plugin-src/client/channels/wecom/styles.js
|
|
5144
5441
|
var WECOM_STYLE_ID = "xmanrui-dsh-im-wecom-settings";
|
|
@@ -5163,7 +5460,7 @@ function installWecomStyles() {
|
|
|
5163
5460
|
|
|
5164
5461
|
// plugin-src/client/channels/wecom/index.js
|
|
5165
5462
|
var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
|
|
5166
|
-
var Button9 =
|
|
5463
|
+
var Button9 = React12.forwardRef(function Button10({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
5167
5464
|
return h2("button", {
|
|
5168
5465
|
...props,
|
|
5169
5466
|
ref,
|
|
@@ -5370,7 +5667,16 @@ function RemoveConfirmation4({ account, busy, onConfirm, onCancel }) {
|
|
|
5370
5667
|
)
|
|
5371
5668
|
);
|
|
5372
5669
|
}
|
|
5373
|
-
function AccountCard3({
|
|
5670
|
+
function AccountCard3({
|
|
5671
|
+
account,
|
|
5672
|
+
busy,
|
|
5673
|
+
removing,
|
|
5674
|
+
onReconnect,
|
|
5675
|
+
onWorkspaceSave,
|
|
5676
|
+
onRequestRemove,
|
|
5677
|
+
onConfirmRemove,
|
|
5678
|
+
onCancelRemove
|
|
5679
|
+
}) {
|
|
5374
5680
|
const tone = account.connected ? "success" : account.state === "error" ? "error" : "warning";
|
|
5375
5681
|
const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : account.state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
|
|
5376
5682
|
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
|
|
@@ -5407,6 +5713,11 @@ function AccountCard3({ account, busy, removing, onReconnect, onRequestRemove, o
|
|
|
5407
5713
|
h2("div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6D88\u606F\u901A\u9053"), h2("dd", null, account.connected ? "WebSocket \u957F\u8FDE\u63A5" : "\u79BB\u7EBF")),
|
|
5408
5714
|
h2("div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h2("dd", null, checkedTime4(account.health.lastCheckedAt)))
|
|
5409
5715
|
),
|
|
5716
|
+
h2(WorkspaceEditor, {
|
|
5717
|
+
workspace: account.workspace,
|
|
5718
|
+
disabled: Boolean(busy),
|
|
5719
|
+
onSave: onWorkspaceSave
|
|
5720
|
+
}),
|
|
5410
5721
|
h2(
|
|
5411
5722
|
"div",
|
|
5412
5723
|
{ className: "ddt-accountFooter dim-cardFooter" },
|
|
@@ -5428,17 +5739,18 @@ function AccountCard3({ account, busy, removing, onReconnect, onRequestRemove, o
|
|
|
5428
5739
|
);
|
|
5429
5740
|
}
|
|
5430
5741
|
function WecomSettingsTab({ rpcCall }) {
|
|
5431
|
-
const [model, setModel] =
|
|
5432
|
-
const [provision, setProvision] =
|
|
5433
|
-
const [busy, setBusy] =
|
|
5434
|
-
const [busyByBot, setBusyByBot] =
|
|
5435
|
-
const [removeTarget, setRemoveTarget] =
|
|
5436
|
-
const [credentialOpen, setCredentialOpen] =
|
|
5437
|
-
const [credentialError, setCredentialError] =
|
|
5438
|
-
const [now, setNow] =
|
|
5439
|
-
const mounted =
|
|
5440
|
-
const
|
|
5441
|
-
|
|
5742
|
+
const [model, setModel] = React12.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
|
|
5743
|
+
const [provision, setProvision] = React12.useState(null);
|
|
5744
|
+
const [busy, setBusy] = React12.useState(false);
|
|
5745
|
+
const [busyByBot, setBusyByBot] = React12.useState({});
|
|
5746
|
+
const [removeTarget, setRemoveTarget] = React12.useState(null);
|
|
5747
|
+
const [credentialOpen, setCredentialOpen] = React12.useState(false);
|
|
5748
|
+
const [credentialError, setCredentialError] = React12.useState(null);
|
|
5749
|
+
const [now, setNow] = React12.useState(Date.now());
|
|
5750
|
+
const mounted = React12.useRef(true);
|
|
5751
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
5752
|
+
const addButtonRef = React12.useRef(null);
|
|
5753
|
+
React12.useEffect(() => {
|
|
5442
5754
|
const disposeDingtalk = installDingtalkStyles();
|
|
5443
5755
|
const disposeWecom = installWecomStyles();
|
|
5444
5756
|
mounted.current = true;
|
|
@@ -5448,15 +5760,17 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5448
5760
|
disposeDingtalk();
|
|
5449
5761
|
};
|
|
5450
5762
|
}, []);
|
|
5451
|
-
const invoke =
|
|
5763
|
+
const invoke = React12.useCallback(async (endpoint, payload = {}, signal) => {
|
|
5452
5764
|
if (typeof rpcCall !== "function") throw new TypeError("\u4F01\u4E1A\u5FAE\u4FE1\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
|
|
5453
5765
|
return unwrapRpcResult7(await rpcCall(endpoint, payload, signal));
|
|
5454
5766
|
}, [rpcCall]);
|
|
5455
|
-
const loadStatus =
|
|
5767
|
+
const loadStatus = React12.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
5768
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
5769
|
+
if (workspaceVersion === null) return void 0;
|
|
5456
5770
|
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
5457
5771
|
try {
|
|
5458
5772
|
const snapshot = normalizeSnapshot6(await invoke(WECOM_ENDPOINTS.status, {}, signal));
|
|
5459
|
-
if (!mounted.current || signal?.aborted) return void 0;
|
|
5773
|
+
if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
5460
5774
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
5461
5775
|
if (restore && snapshot.provisioning) setProvision({
|
|
5462
5776
|
...snapshot.provisioning,
|
|
@@ -5464,18 +5778,18 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5464
5778
|
});
|
|
5465
5779
|
return snapshot;
|
|
5466
5780
|
} catch (error) {
|
|
5467
|
-
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
|
|
5781
|
+
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted && workspaceFence.canCommitStatus(workspaceVersion)) {
|
|
5468
5782
|
setModel((current) => ({ ...current, phase: silent ? current.phase : "error", error: presentError7(error) }));
|
|
5469
5783
|
}
|
|
5470
5784
|
return void 0;
|
|
5471
5785
|
}
|
|
5472
|
-
}, [invoke]);
|
|
5473
|
-
|
|
5786
|
+
}, [invoke, workspaceFence]);
|
|
5787
|
+
React12.useEffect(() => {
|
|
5474
5788
|
const controller = new AbortController();
|
|
5475
5789
|
void loadStatus({ signal: controller.signal, restore: true });
|
|
5476
5790
|
return () => controller.abort();
|
|
5477
5791
|
}, [loadStatus]);
|
|
5478
|
-
|
|
5792
|
+
React12.useEffect(() => {
|
|
5479
5793
|
if (model.phase !== "ready") return void 0;
|
|
5480
5794
|
const controller = new AbortController();
|
|
5481
5795
|
const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
|
|
@@ -5484,12 +5798,12 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5484
5798
|
window.clearInterval(timer);
|
|
5485
5799
|
};
|
|
5486
5800
|
}, [loadStatus, model.phase]);
|
|
5487
|
-
|
|
5801
|
+
React12.useEffect(() => {
|
|
5488
5802
|
if (!provision || !ACTIVE_STATES2.has(provision.status)) return void 0;
|
|
5489
5803
|
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
|
|
5490
5804
|
return () => window.clearInterval(timer);
|
|
5491
5805
|
}, [provision?.attemptId, provision?.status]);
|
|
5492
|
-
const startProvisioning =
|
|
5806
|
+
const startProvisioning = React12.useCallback(async (replace = false) => {
|
|
5493
5807
|
setCredentialOpen(false);
|
|
5494
5808
|
setCredentialError(null);
|
|
5495
5809
|
setBusy(true);
|
|
@@ -5507,7 +5821,8 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5507
5821
|
if (mounted.current) setBusy(false);
|
|
5508
5822
|
}
|
|
5509
5823
|
}, [invoke, provision?.attemptId]);
|
|
5510
|
-
const bindCredentials =
|
|
5824
|
+
const bindCredentials = React12.useCallback(async ({ identity, secret }) => {
|
|
5825
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
5511
5826
|
setBusy(true);
|
|
5512
5827
|
setCredentialError(null);
|
|
5513
5828
|
try {
|
|
@@ -5516,15 +5831,19 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5516
5831
|
{ botId: identity, secret }
|
|
5517
5832
|
));
|
|
5518
5833
|
if (!mounted.current) return;
|
|
5519
|
-
|
|
5834
|
+
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
5835
|
+
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
5836
|
+
}
|
|
5520
5837
|
setCredentialOpen(false);
|
|
5521
5838
|
} catch (error) {
|
|
5522
5839
|
if (mounted.current) setCredentialError(presentError7(error));
|
|
5523
5840
|
} finally {
|
|
5841
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
5842
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
5524
5843
|
if (mounted.current) setBusy(false);
|
|
5525
5844
|
}
|
|
5526
|
-
}, [invoke]);
|
|
5527
|
-
const closeProvision =
|
|
5845
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
5846
|
+
const closeProvision = React12.useCallback(async () => {
|
|
5528
5847
|
setBusy(true);
|
|
5529
5848
|
try {
|
|
5530
5849
|
if (provision?.attemptId && ACTIVE_STATES2.has(provision.status)) {
|
|
@@ -5535,7 +5854,7 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5535
5854
|
if (mounted.current) setBusy(false);
|
|
5536
5855
|
}
|
|
5537
5856
|
}, [invoke, provision?.attemptId, provision?.status]);
|
|
5538
|
-
|
|
5857
|
+
React12.useEffect(() => {
|
|
5539
5858
|
const attemptId = provision?.attemptId;
|
|
5540
5859
|
if (!attemptId || !ACTIVE_STATES2.has(provision.status)) return void 0;
|
|
5541
5860
|
const controller = new AbortController();
|
|
@@ -5565,20 +5884,25 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5565
5884
|
window.clearTimeout(timer);
|
|
5566
5885
|
};
|
|
5567
5886
|
}, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
|
|
5568
|
-
const botAction =
|
|
5887
|
+
const botAction = React12.useCallback(async (account, operation, endpoint, payload) => {
|
|
5888
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
5569
5889
|
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
|
5570
5890
|
try {
|
|
5571
5891
|
const snapshot = normalizeSnapshot6(await invoke(endpoint, payload));
|
|
5572
|
-
if (mounted.current
|
|
5892
|
+
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
5893
|
+
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
5894
|
+
}
|
|
5573
5895
|
return snapshot;
|
|
5574
5896
|
} finally {
|
|
5897
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
5898
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
5575
5899
|
if (mounted.current) setBusyByBot((current) => {
|
|
5576
5900
|
const next = { ...current };
|
|
5577
5901
|
delete next[account.botId];
|
|
5578
5902
|
return next;
|
|
5579
5903
|
});
|
|
5580
5904
|
}
|
|
5581
|
-
}, [invoke]);
|
|
5905
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
5582
5906
|
let provisionView = null;
|
|
5583
5907
|
if (provision?.status === "starting") provisionView = h2("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h2("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7\u4F01\u4E1A\u5FAE\u4FE1\u4E8C\u7EF4\u7801\u2026");
|
|
5584
5908
|
else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h2(QrPanel3, {
|
|
@@ -5607,6 +5931,12 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5607
5931
|
busy: busyByBot[account.botId],
|
|
5608
5932
|
removing: removeTarget === account.botId,
|
|
5609
5933
|
onReconnect: () => void botAction(account, "reconnect", WECOM_ENDPOINTS.reconnectBot, { botId: account.botId }),
|
|
5934
|
+
onWorkspaceSave: (workspace) => botAction(
|
|
5935
|
+
account,
|
|
5936
|
+
"workspace",
|
|
5937
|
+
WECOM_ENDPOINTS.setWorkspace,
|
|
5938
|
+
{ botId: account.botId, workspace }
|
|
5939
|
+
),
|
|
5610
5940
|
onRequestRemove: () => setRemoveTarget(account.botId),
|
|
5611
5941
|
onCancelRemove: () => setRemoveTarget(null),
|
|
5612
5942
|
onConfirmRemove: async () => {
|
|
@@ -5645,7 +5975,7 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5645
5975
|
addButtonRef
|
|
5646
5976
|
}),
|
|
5647
5977
|
model.phase === "loading" ? h2(LoadingView4) : model.phase === "error" ? h2("div", { className: "ddt-card dim-surfaceCard" }, h2("div", { className: "ddt-inlineError dim-inlineError" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message), h2(Button9, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h2(
|
|
5648
|
-
|
|
5978
|
+
React12.Fragment,
|
|
5649
5979
|
null,
|
|
5650
5980
|
credentialView,
|
|
5651
5981
|
provisionView,
|
|
@@ -5656,7 +5986,7 @@ function WecomSettingsTab({ rpcCall }) {
|
|
|
5656
5986
|
}
|
|
5657
5987
|
|
|
5658
5988
|
// plugin-src/client/channels/weixin/index.js
|
|
5659
|
-
var
|
|
5989
|
+
var React13 = __toESM(require("react"), 1);
|
|
5660
5990
|
|
|
5661
5991
|
// plugin-src/client/channels/weixin/api.js
|
|
5662
5992
|
var WEIXIN_RPC_CHANNEL = "/weixin";
|
|
@@ -5667,7 +5997,8 @@ var WEIXIN_ENDPOINTS = Object.freeze({
|
|
|
5667
5997
|
submitVerification: "provision.verify",
|
|
5668
5998
|
cancelProvisioning: "provision.cancel",
|
|
5669
5999
|
reconnectBot: "bot.reconnect",
|
|
5670
|
-
deleteBot: "bot.delete"
|
|
6000
|
+
deleteBot: "bot.delete",
|
|
6001
|
+
setWorkspace: "bot.workspace.set"
|
|
5671
6002
|
});
|
|
5672
6003
|
var ACCOUNT_STATES5 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
|
|
5673
6004
|
var PROVISION_STATES4 = /* @__PURE__ */ new Set([
|
|
@@ -5749,6 +6080,7 @@ function normalizeBot5(value) {
|
|
|
5749
6080
|
state: connected ? "connected" : state,
|
|
5750
6081
|
connected,
|
|
5751
6082
|
configured: value.configured === true,
|
|
6083
|
+
workspace: string(value.workspace).slice(0, 4096),
|
|
5752
6084
|
bot: {
|
|
5753
6085
|
name: string(value.bot.name, "\u5FAE\u4FE1\u673A\u5668\u4EBA"),
|
|
5754
6086
|
accountIdMasked: string(value.bot.accountIdMasked, "\u5DF2\u5B89\u5168\u4FDD\u5B58")
|
|
@@ -5919,7 +6251,7 @@ function installWeixinStyles() {
|
|
|
5919
6251
|
}
|
|
5920
6252
|
|
|
5921
6253
|
// plugin-src/client/channels/weixin/index.js
|
|
5922
|
-
var Button11 =
|
|
6254
|
+
var Button11 = React13.forwardRef(function Button12({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
5923
6255
|
return h2("button", {
|
|
5924
6256
|
...props,
|
|
5925
6257
|
ref,
|
|
@@ -5992,14 +6324,14 @@ function EmptyView5({ onStart, busy }) {
|
|
|
5992
6324
|
);
|
|
5993
6325
|
}
|
|
5994
6326
|
function QrPanel4({ provision, now, busy, onRefresh, onCancel }) {
|
|
5995
|
-
const [imageFailed, setImageFailed] =
|
|
6327
|
+
const [imageFailed, setImageFailed] = React13.useState(false);
|
|
5996
6328
|
const source = safeQrSource5(provision.qrCodeDataUrl);
|
|
5997
6329
|
const href = safeVerificationUrl(provision.verificationUrl);
|
|
5998
6330
|
const remaining = Math.max(0, provision.expiresAt - now);
|
|
5999
6331
|
const expired = remaining === 0 || provision.status === "expired";
|
|
6000
6332
|
const duration = Math.max(1, provision.durationMs ?? 5 * 6e4);
|
|
6001
6333
|
const progress = Math.round(Math.min(1, remaining / duration) * 100);
|
|
6002
|
-
|
|
6334
|
+
React13.useEffect(() => setImageFailed(false), [source]);
|
|
6003
6335
|
return h2(
|
|
6004
6336
|
"div",
|
|
6005
6337
|
{ className: "dxw-card dim-surfaceCard" },
|
|
@@ -6066,9 +6398,9 @@ function QrPanel4({ provision, now, busy, onRefresh, onCancel }) {
|
|
|
6066
6398
|
);
|
|
6067
6399
|
}
|
|
6068
6400
|
function VerificationPanel({ provision, busy, onSubmit, onCancel }) {
|
|
6069
|
-
const [code, setCode] =
|
|
6401
|
+
const [code, setCode] = React13.useState("");
|
|
6070
6402
|
const valid = /^\d{4,8}$/.test(code);
|
|
6071
|
-
|
|
6403
|
+
React13.useEffect(() => setCode(""), [provision.attemptId]);
|
|
6072
6404
|
return h2(
|
|
6073
6405
|
"div",
|
|
6074
6406
|
{ className: "dxw-card dim-surfaceCard" },
|
|
@@ -6159,7 +6491,16 @@ function checkedTime5(timestamp7) {
|
|
|
6159
6491
|
return "\u521A\u521A";
|
|
6160
6492
|
}
|
|
6161
6493
|
}
|
|
6162
|
-
function AccountCard4({
|
|
6494
|
+
function AccountCard4({
|
|
6495
|
+
account,
|
|
6496
|
+
busy,
|
|
6497
|
+
removing,
|
|
6498
|
+
onReconnect,
|
|
6499
|
+
onWorkspaceSave,
|
|
6500
|
+
onRequestRemove,
|
|
6501
|
+
onConfirmRemove,
|
|
6502
|
+
onCancelRemove
|
|
6503
|
+
}) {
|
|
6163
6504
|
const state = busy === "reconnect" ? "connecting" : account.state;
|
|
6164
6505
|
const tone = account.connected ? "success" : state === "error" ? "error" : "warning";
|
|
6165
6506
|
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
|
|
@@ -6201,6 +6542,11 @@ function AccountCard4({ account, busy, removing, onReconnect, onRequestRemove, o
|
|
|
6201
6542
|
h2("dd", null, checkedTime5(account.health.lastCheckedAt))
|
|
6202
6543
|
)
|
|
6203
6544
|
),
|
|
6545
|
+
h2(WorkspaceEditor, {
|
|
6546
|
+
workspace: account.workspace,
|
|
6547
|
+
disabled: Boolean(busy),
|
|
6548
|
+
onSave: onWorkspaceSave
|
|
6549
|
+
}),
|
|
6204
6550
|
h2(
|
|
6205
6551
|
"div",
|
|
6206
6552
|
{ className: "dxw-accountFooter dim-cardFooter" },
|
|
@@ -6248,6 +6594,7 @@ function AccountList2(props) {
|
|
|
6248
6594
|
busy: props.busyByBot[account.botId],
|
|
6249
6595
|
removing: props.removeTarget === account.botId,
|
|
6250
6596
|
onReconnect: () => props.onReconnect(account),
|
|
6597
|
+
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
|
6251
6598
|
onRequestRemove: () => props.onRequestRemove(account),
|
|
6252
6599
|
onConfirmRemove: () => props.onConfirmRemove(account),
|
|
6253
6600
|
onCancelRemove: props.onCancelRemove
|
|
@@ -6266,39 +6613,49 @@ function mergeWeixinProvisioningSnapshot(current, incoming, { restoreProvisionin
|
|
|
6266
6613
|
};
|
|
6267
6614
|
}
|
|
6268
6615
|
function WeixinSettingsTab({ rpcCall }) {
|
|
6269
|
-
const [model, setModel] =
|
|
6616
|
+
const [model, setModel] = React13.useState({
|
|
6270
6617
|
phase: "loading",
|
|
6271
6618
|
bots: [],
|
|
6272
6619
|
totals: EMPTY_TOTALS3,
|
|
6273
6620
|
revision: 0,
|
|
6274
6621
|
error: null
|
|
6275
6622
|
});
|
|
6276
|
-
const [provision, setProvision] =
|
|
6277
|
-
const [busy, setBusy] =
|
|
6278
|
-
const [busyByBot, setBusyByBot] =
|
|
6279
|
-
const [removeTarget, setRemoveTarget] =
|
|
6280
|
-
const [notice, setNotice] =
|
|
6281
|
-
const [now, setNow] =
|
|
6282
|
-
const addButtonRef =
|
|
6623
|
+
const [provision, setProvision] = React13.useState(null);
|
|
6624
|
+
const [busy, setBusy] = React13.useState(false);
|
|
6625
|
+
const [busyByBot, setBusyByBot] = React13.useState({});
|
|
6626
|
+
const [removeTarget, setRemoveTarget] = React13.useState(null);
|
|
6627
|
+
const [notice, setNotice] = React13.useState("");
|
|
6628
|
+
const [now, setNow] = React13.useState(() => Date.now());
|
|
6629
|
+
const addButtonRef = React13.useRef(null);
|
|
6630
|
+
const mountedRef = React13.useRef(true);
|
|
6631
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
6283
6632
|
const scheduleAnimationFrame = useAnimationFrameScheduler();
|
|
6284
|
-
|
|
6633
|
+
React13.useEffect(() => {
|
|
6634
|
+
mountedRef.current = true;
|
|
6635
|
+
return () => {
|
|
6636
|
+
mountedRef.current = false;
|
|
6637
|
+
};
|
|
6638
|
+
}, []);
|
|
6639
|
+
const announce = React13.useCallback((value) => {
|
|
6285
6640
|
setNotice("");
|
|
6286
6641
|
scheduleAnimationFrame(() => {
|
|
6287
6642
|
if (value) setNotice(value);
|
|
6288
6643
|
}, "announcement");
|
|
6289
6644
|
}, [scheduleAnimationFrame]);
|
|
6290
|
-
const invoke =
|
|
6645
|
+
const invoke = React13.useCallback(async (endpoint, payload = {}, signal) => {
|
|
6291
6646
|
return unwrapRpcResult8(await rpcCall(endpoint, payload, signal));
|
|
6292
6647
|
}, [rpcCall]);
|
|
6293
|
-
const loadStatus =
|
|
6648
|
+
const loadStatus = React13.useCallback(async ({
|
|
6294
6649
|
signal,
|
|
6295
6650
|
silent = false,
|
|
6296
6651
|
restoreProvisioning = false
|
|
6297
6652
|
} = {}) => {
|
|
6653
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
6654
|
+
if (workspaceVersion === null || !mountedRef.current) return void 0;
|
|
6298
6655
|
if (!silent) setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
6299
6656
|
try {
|
|
6300
6657
|
const snapshot = normalizeSnapshot7(await invoke(WEIXIN_ENDPOINTS.status, {}, signal));
|
|
6301
|
-
if (signal?.aborted) return void 0;
|
|
6658
|
+
if (signal?.aborted || !mountedRef.current || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
6302
6659
|
setModel({
|
|
6303
6660
|
phase: "ready",
|
|
6304
6661
|
bots: snapshot.bots,
|
|
@@ -6315,7 +6672,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6315
6672
|
}
|
|
6316
6673
|
return snapshot;
|
|
6317
6674
|
} catch (error) {
|
|
6318
|
-
if (signal?.aborted || error?.name === "AbortError") return void 0;
|
|
6675
|
+
if (signal?.aborted || error?.name === "AbortError" || !mountedRef.current || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
6319
6676
|
setModel((current) => ({
|
|
6320
6677
|
...current,
|
|
6321
6678
|
phase: silent && current.phase === "ready" ? "ready" : "error",
|
|
@@ -6323,13 +6680,13 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6323
6680
|
}));
|
|
6324
6681
|
return void 0;
|
|
6325
6682
|
}
|
|
6326
|
-
}, [invoke]);
|
|
6327
|
-
|
|
6683
|
+
}, [invoke, workspaceFence]);
|
|
6684
|
+
React13.useEffect(() => {
|
|
6328
6685
|
const controller = new AbortController();
|
|
6329
6686
|
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
|
|
6330
6687
|
return () => controller.abort();
|
|
6331
6688
|
}, [loadStatus]);
|
|
6332
|
-
|
|
6689
|
+
React13.useEffect(() => {
|
|
6333
6690
|
if (model.phase !== "ready") return void 0;
|
|
6334
6691
|
const controller = new AbortController();
|
|
6335
6692
|
let running = false;
|
|
@@ -6348,12 +6705,12 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6348
6705
|
window.clearInterval(timer);
|
|
6349
6706
|
};
|
|
6350
6707
|
}, [loadStatus, model.phase]);
|
|
6351
|
-
|
|
6708
|
+
React13.useEffect(() => {
|
|
6352
6709
|
if (!provision || !["pending", "scanned"].includes(provision.status)) return void 0;
|
|
6353
6710
|
const timer = window.setInterval(() => setNow(Date.now()), 1e3);
|
|
6354
6711
|
return () => window.clearInterval(timer);
|
|
6355
6712
|
}, [provision?.attemptId, provision?.status]);
|
|
6356
|
-
const startProvisioning =
|
|
6713
|
+
const startProvisioning = React13.useCallback(async ({ replace = false } = {}) => {
|
|
6357
6714
|
setBusy(true);
|
|
6358
6715
|
try {
|
|
6359
6716
|
if (replace && provision?.attemptId) {
|
|
@@ -6374,7 +6731,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6374
6731
|
setBusy(false);
|
|
6375
6732
|
}
|
|
6376
6733
|
}, [announce, invoke, provision?.attemptId]);
|
|
6377
|
-
const cancelProvisioning =
|
|
6734
|
+
const cancelProvisioning = React13.useCallback(async () => {
|
|
6378
6735
|
setBusy(true);
|
|
6379
6736
|
try {
|
|
6380
6737
|
if (provision?.attemptId && !["failed", "expired", "cancelled"].includes(provision.status)) {
|
|
@@ -6389,7 +6746,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6389
6746
|
setBusy(false);
|
|
6390
6747
|
}
|
|
6391
6748
|
}, [announce, invoke, provision?.attemptId, provision?.status, scheduleAnimationFrame]);
|
|
6392
|
-
const submitVerification =
|
|
6749
|
+
const submitVerification = React13.useCallback(async (verifyCode) => {
|
|
6393
6750
|
if (!provision?.attemptId) return;
|
|
6394
6751
|
setBusy(true);
|
|
6395
6752
|
try {
|
|
@@ -6405,7 +6762,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6405
6762
|
setBusy(false);
|
|
6406
6763
|
}
|
|
6407
6764
|
}, [announce, invoke, provision?.attemptId]);
|
|
6408
|
-
|
|
6765
|
+
React13.useEffect(() => {
|
|
6409
6766
|
const attemptId = provision?.attemptId;
|
|
6410
6767
|
if (!attemptId || !["pending", "scanned", "connecting"].includes(provision.status)) return void 0;
|
|
6411
6768
|
const controller = new AbortController();
|
|
@@ -6453,7 +6810,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6453
6810
|
controller.abort();
|
|
6454
6811
|
};
|
|
6455
6812
|
}, [announce, invoke, loadStatus, provision?.attemptId, provision?.status, provision?.pollIntervalMs]);
|
|
6456
|
-
const setBotBusy =
|
|
6813
|
+
const setBotBusy = React13.useCallback((botId, value) => {
|
|
6457
6814
|
setBusyByBot((current) => {
|
|
6458
6815
|
const next = { ...current };
|
|
6459
6816
|
if (value) next[botId] = value;
|
|
@@ -6461,35 +6818,68 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6461
6818
|
return next;
|
|
6462
6819
|
});
|
|
6463
6820
|
}, []);
|
|
6464
|
-
const reconnect =
|
|
6821
|
+
const reconnect = React13.useCallback(async (account) => {
|
|
6822
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
6465
6823
|
setBotBusy(account.botId, "reconnect");
|
|
6466
6824
|
try {
|
|
6467
6825
|
const snapshot = normalizeSnapshot7(await invoke(WEIXIN_ENDPOINTS.reconnectBot, { botId: account.botId }));
|
|
6468
|
-
|
|
6826
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
6827
|
+
setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
|
|
6828
|
+
}
|
|
6469
6829
|
const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
|
|
6470
6830
|
announce(refreshed?.connected ? "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002" : "\u5FAE\u4FE1\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002");
|
|
6471
6831
|
} catch (error) {
|
|
6472
6832
|
announce(`\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF1A${presentError8(error).message}`);
|
|
6473
6833
|
} finally {
|
|
6834
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
6835
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
6474
6836
|
setBotBusy(account.botId, null);
|
|
6475
6837
|
}
|
|
6476
|
-
}, [announce, invoke, setBotBusy]);
|
|
6477
|
-
const
|
|
6838
|
+
}, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
6839
|
+
const saveWorkspace = React13.useCallback(async (account, workspace) => {
|
|
6840
|
+
const workspaceVersion = workspaceFence.beginMutation();
|
|
6841
|
+
setBotBusy(account.botId, "workspace");
|
|
6842
|
+
try {
|
|
6843
|
+
const snapshot = normalizeSnapshot7(await invoke(
|
|
6844
|
+
WEIXIN_ENDPOINTS.setWorkspace,
|
|
6845
|
+
{ botId: account.botId, workspace }
|
|
6846
|
+
));
|
|
6847
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(workspaceVersion)) {
|
|
6848
|
+
setModel({
|
|
6849
|
+
phase: "ready",
|
|
6850
|
+
bots: snapshot.bots,
|
|
6851
|
+
totals: snapshot.totals,
|
|
6852
|
+
revision: snapshot.revision,
|
|
6853
|
+
error: null
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
} finally {
|
|
6857
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
6858
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
6859
|
+
if (mountedRef.current) setBotBusy(account.botId, null);
|
|
6860
|
+
}
|
|
6861
|
+
}, [invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
6862
|
+
const remove = React13.useCallback(async (account) => {
|
|
6863
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
6478
6864
|
setBotBusy(account.botId, "delete");
|
|
6479
6865
|
try {
|
|
6480
6866
|
const snapshot = normalizeSnapshot7(await invoke(WEIXIN_ENDPOINTS.deleteBot, {
|
|
6481
6867
|
botId: account.botId,
|
|
6482
6868
|
confirm: true
|
|
6483
6869
|
}));
|
|
6484
|
-
|
|
6870
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
6871
|
+
setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
|
|
6872
|
+
}
|
|
6485
6873
|
setRemoveTarget(null);
|
|
6486
6874
|
announce("\u5FAE\u4FE1\u8D26\u53F7\u53CA\u672C\u673A\u51ED\u636E\u5DF2\u79FB\u9664\u3002");
|
|
6487
6875
|
} catch (error) {
|
|
6488
6876
|
announce(`\u79FB\u9664\u5931\u8D25\uFF1A${presentError8(error).message}`);
|
|
6489
6877
|
} finally {
|
|
6878
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
6879
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
6490
6880
|
setBotBusy(account.botId, null);
|
|
6491
6881
|
}
|
|
6492
|
-
}, [announce, invoke, setBotBusy]);
|
|
6882
|
+
}, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
6493
6883
|
let provisionView = null;
|
|
6494
6884
|
if (provision?.status === "starting") {
|
|
6495
6885
|
provisionView = h2(ProgressPanel2, { busy });
|
|
@@ -6545,7 +6935,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6545
6935
|
h2(Button11, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
|
|
6546
6936
|
)
|
|
6547
6937
|
) : h2(
|
|
6548
|
-
|
|
6938
|
+
React13.Fragment,
|
|
6549
6939
|
null,
|
|
6550
6940
|
provisionView,
|
|
6551
6941
|
model.bots.length === 0 && !provision ? h2(EmptyView5, { onStart: () => void startProvisioning(), busy }) : null,
|
|
@@ -6554,6 +6944,7 @@ function WeixinSettingsTab({ rpcCall }) {
|
|
|
6554
6944
|
busyByBot,
|
|
6555
6945
|
removeTarget,
|
|
6556
6946
|
onReconnect: (account) => void reconnect(account),
|
|
6947
|
+
onWorkspaceSave: saveWorkspace,
|
|
6557
6948
|
onRequestRemove: (account) => setRemoveTarget(account.botId),
|
|
6558
6949
|
onConfirmRemove: (account) => void remove(account),
|
|
6559
6950
|
onCancelRemove: () => setRemoveTarget(null)
|
|
@@ -6570,7 +6961,8 @@ var WHATSAPP_ENDPOINTS = Object.freeze({
|
|
|
6570
6961
|
pollProvisioning: "provision.poll",
|
|
6571
6962
|
cancelProvisioning: "provision.cancel",
|
|
6572
6963
|
reconnectBot: "bot.reconnect",
|
|
6573
|
-
deleteBot: "bot.delete"
|
|
6964
|
+
deleteBot: "bot.delete",
|
|
6965
|
+
setWorkspace: "bot.workspace.set"
|
|
6574
6966
|
});
|
|
6575
6967
|
var PROVISION_STATES5 = /* @__PURE__ */ new Set(["starting", "pending", "connecting", "connected", "failed", "cancelled"]);
|
|
6576
6968
|
var BOT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
|
|
@@ -6634,6 +7026,7 @@ function normalizeBot6(value) {
|
|
|
6634
7026
|
botId: id4(value.botId),
|
|
6635
7027
|
connected,
|
|
6636
7028
|
state: connected ? "connected" : state,
|
|
7029
|
+
workspace: text4(value.workspace, "", 4096),
|
|
6637
7030
|
bot: {
|
|
6638
7031
|
name: text4(value.bot?.name, "WhatsApp\u673A\u5668\u4EBA", 100),
|
|
6639
7032
|
idMasked: text4(value.bot?.idMasked, "WhatsApp\u8D26\u53F7", 140)
|
|
@@ -6673,7 +7066,7 @@ function formatRemaining6(milliseconds) {
|
|
|
6673
7066
|
}
|
|
6674
7067
|
|
|
6675
7068
|
// plugin-src/client/channels/whatsapp/index.js
|
|
6676
|
-
var
|
|
7069
|
+
var React14 = __toESM(require("react"), 1);
|
|
6677
7070
|
|
|
6678
7071
|
// plugin-src/client/channels/whatsapp/styles.js
|
|
6679
7072
|
var WHATSAPP_STYLE_ID = "xmanrui-dsh-im-whatsapp-settings";
|
|
@@ -6698,7 +7091,7 @@ function installWhatsappStyles() {
|
|
|
6698
7091
|
|
|
6699
7092
|
// plugin-src/client/channels/whatsapp/index.js
|
|
6700
7093
|
var ACTIVE_STATES3 = /* @__PURE__ */ new Set(["pending", "connecting"]);
|
|
6701
|
-
var Button13 =
|
|
7094
|
+
var Button13 = React14.forwardRef(function Button14({ children, kind = "secondary", className = "", ...props }, ref) {
|
|
6702
7095
|
return h2("button", {
|
|
6703
7096
|
...props,
|
|
6704
7097
|
ref,
|
|
@@ -6909,6 +7302,7 @@ function WhatsappAccountCard({
|
|
|
6909
7302
|
busy,
|
|
6910
7303
|
removing,
|
|
6911
7304
|
onReconnect,
|
|
7305
|
+
onWorkspaceSave,
|
|
6912
7306
|
onRequestRemove,
|
|
6913
7307
|
onConfirmRemove,
|
|
6914
7308
|
onCancelRemove
|
|
@@ -6963,6 +7357,11 @@ function WhatsappAccountCard({
|
|
|
6963
7357
|
h2("dd", null, checkedTime6(account.health.lastCheckedAt))
|
|
6964
7358
|
)
|
|
6965
7359
|
),
|
|
7360
|
+
h2(WorkspaceEditor, {
|
|
7361
|
+
workspace: account.workspace,
|
|
7362
|
+
disabled: Boolean(busy),
|
|
7363
|
+
onSave: onWorkspaceSave
|
|
7364
|
+
}),
|
|
6966
7365
|
h2(
|
|
6967
7366
|
"div",
|
|
6968
7367
|
{ className: "ddt-accountFooter dim-cardFooter" },
|
|
@@ -6993,20 +7392,21 @@ function WhatsappAccountCard({
|
|
|
6993
7392
|
);
|
|
6994
7393
|
}
|
|
6995
7394
|
function WhatsappSettingsTab({ rpcCall }) {
|
|
6996
|
-
const [model, setModel] =
|
|
7395
|
+
const [model, setModel] = React14.useState({
|
|
6997
7396
|
phase: "loading",
|
|
6998
7397
|
bots: [],
|
|
6999
7398
|
totals: { configured: 0, connected: 0 },
|
|
7000
7399
|
error: null
|
|
7001
7400
|
});
|
|
7002
|
-
const [provision, setProvision] =
|
|
7003
|
-
const [busy, setBusy] =
|
|
7004
|
-
const [busyByBot, setBusyByBot] =
|
|
7005
|
-
const [removeTarget, setRemoveTarget] =
|
|
7006
|
-
const [now, setNow] =
|
|
7007
|
-
const mounted =
|
|
7008
|
-
const
|
|
7009
|
-
|
|
7401
|
+
const [provision, setProvision] = React14.useState(null);
|
|
7402
|
+
const [busy, setBusy] = React14.useState(false);
|
|
7403
|
+
const [busyByBot, setBusyByBot] = React14.useState({});
|
|
7404
|
+
const [removeTarget, setRemoveTarget] = React14.useState(null);
|
|
7405
|
+
const [now, setNow] = React14.useState(Date.now());
|
|
7406
|
+
const mounted = React14.useRef(true);
|
|
7407
|
+
const workspaceFence = useWorkspaceSnapshotFence();
|
|
7408
|
+
const addButtonRef = React14.useRef(null);
|
|
7409
|
+
React14.useEffect(() => {
|
|
7010
7410
|
const disposeDingtalk = installDingtalkStyles();
|
|
7011
7411
|
const disposeWhatsapp = installWhatsappStyles();
|
|
7012
7412
|
mounted.current = true;
|
|
@@ -7016,15 +7416,17 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7016
7416
|
disposeDingtalk();
|
|
7017
7417
|
};
|
|
7018
7418
|
}, []);
|
|
7019
|
-
const invoke =
|
|
7419
|
+
const invoke = React14.useCallback(async (endpoint, payload = {}, signal) => {
|
|
7020
7420
|
if (typeof rpcCall !== "function") throw new TypeError("WhatsApp \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
|
|
7021
7421
|
return unwrapRpcResult9(await rpcCall(endpoint, payload, signal));
|
|
7022
7422
|
}, [rpcCall]);
|
|
7023
|
-
const loadStatus =
|
|
7423
|
+
const loadStatus = React14.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
7424
|
+
const workspaceVersion = workspaceFence.beginStatus();
|
|
7425
|
+
if (workspaceVersion === null) return void 0;
|
|
7024
7426
|
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
|
|
7025
7427
|
try {
|
|
7026
7428
|
const snapshot = normalizeSnapshot8(await invoke(WHATSAPP_ENDPOINTS.status, {}, signal));
|
|
7027
|
-
if (!mounted.current || signal?.aborted) return void 0;
|
|
7429
|
+
if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
|
|
7028
7430
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
7029
7431
|
if (restore && snapshot.provisioning) setProvision({
|
|
7030
7432
|
...snapshot.provisioning,
|
|
@@ -7032,7 +7434,7 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7032
7434
|
});
|
|
7033
7435
|
return snapshot;
|
|
7034
7436
|
} catch (error) {
|
|
7035
|
-
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
|
|
7437
|
+
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted && workspaceFence.canCommitStatus(workspaceVersion)) {
|
|
7036
7438
|
setModel((current) => ({
|
|
7037
7439
|
...current,
|
|
7038
7440
|
phase: silent ? current.phase : "error",
|
|
@@ -7041,13 +7443,13 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7041
7443
|
}
|
|
7042
7444
|
return void 0;
|
|
7043
7445
|
}
|
|
7044
|
-
}, [invoke]);
|
|
7045
|
-
|
|
7446
|
+
}, [invoke, workspaceFence]);
|
|
7447
|
+
React14.useEffect(() => {
|
|
7046
7448
|
const controller = new AbortController();
|
|
7047
7449
|
void loadStatus({ signal: controller.signal, restore: true });
|
|
7048
7450
|
return () => controller.abort();
|
|
7049
7451
|
}, [loadStatus]);
|
|
7050
|
-
|
|
7452
|
+
React14.useEffect(() => {
|
|
7051
7453
|
if (model.phase !== "ready") return void 0;
|
|
7052
7454
|
const controller = new AbortController();
|
|
7053
7455
|
const timer = window.setInterval(
|
|
@@ -7059,12 +7461,12 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7059
7461
|
window.clearInterval(timer);
|
|
7060
7462
|
};
|
|
7061
7463
|
}, [loadStatus, model.phase]);
|
|
7062
|
-
|
|
7464
|
+
React14.useEffect(() => {
|
|
7063
7465
|
if (!provision || !ACTIVE_STATES3.has(provision.status)) return void 0;
|
|
7064
7466
|
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
|
|
7065
7467
|
return () => window.clearInterval(timer);
|
|
7066
7468
|
}, [provision?.attemptId, provision?.status]);
|
|
7067
|
-
const startProvisioning =
|
|
7469
|
+
const startProvisioning = React14.useCallback(async (replace = false) => {
|
|
7068
7470
|
setBusy(true);
|
|
7069
7471
|
try {
|
|
7070
7472
|
if (replace && provision?.attemptId) {
|
|
@@ -7082,7 +7484,7 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7082
7484
|
if (mounted.current) setBusy(false);
|
|
7083
7485
|
}
|
|
7084
7486
|
}, [invoke, provision?.attemptId]);
|
|
7085
|
-
const closeProvision =
|
|
7487
|
+
const closeProvision = React14.useCallback(async () => {
|
|
7086
7488
|
setBusy(true);
|
|
7087
7489
|
try {
|
|
7088
7490
|
if (provision?.attemptId && ACTIVE_STATES3.has(provision.status)) {
|
|
@@ -7093,7 +7495,7 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7093
7495
|
if (mounted.current) setBusy(false);
|
|
7094
7496
|
}
|
|
7095
7497
|
}, [invoke, provision?.attemptId, provision?.status]);
|
|
7096
|
-
|
|
7498
|
+
React14.useEffect(() => {
|
|
7097
7499
|
const attemptId = provision?.attemptId;
|
|
7098
7500
|
if (!attemptId || !ACTIVE_STATES3.has(provision.status)) return void 0;
|
|
7099
7501
|
const controller = new AbortController();
|
|
@@ -7134,21 +7536,24 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7134
7536
|
if (timer) window.clearTimeout(timer);
|
|
7135
7537
|
};
|
|
7136
7538
|
}, [invoke, loadStatus, provision?.attemptId, provision?.status]);
|
|
7137
|
-
const botAction =
|
|
7539
|
+
const botAction = React14.useCallback(async (account, operation, endpoint, payload) => {
|
|
7540
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
7138
7541
|
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
|
7139
7542
|
try {
|
|
7140
7543
|
const snapshot = normalizeSnapshot8(await invoke(endpoint, payload));
|
|
7141
|
-
if (mounted.current) {
|
|
7544
|
+
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
7142
7545
|
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
7143
7546
|
}
|
|
7144
7547
|
} finally {
|
|
7548
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
7549
|
+
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
|
7145
7550
|
if (mounted.current) setBusyByBot((current) => {
|
|
7146
7551
|
const next = { ...current };
|
|
7147
7552
|
delete next[account.botId];
|
|
7148
7553
|
return next;
|
|
7149
7554
|
});
|
|
7150
7555
|
}
|
|
7151
|
-
}, [invoke]);
|
|
7556
|
+
}, [invoke, loadStatus, workspaceFence]);
|
|
7152
7557
|
const botList = model.bots.length > 0 ? h2(
|
|
7153
7558
|
"section",
|
|
7154
7559
|
{ className: "dim-listSection" },
|
|
@@ -7167,6 +7572,12 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7167
7572
|
WHATSAPP_ENDPOINTS.reconnectBot,
|
|
7168
7573
|
{ botId: account.botId }
|
|
7169
7574
|
),
|
|
7575
|
+
onWorkspaceSave: (workspace) => botAction(
|
|
7576
|
+
account,
|
|
7577
|
+
"workspace",
|
|
7578
|
+
WHATSAPP_ENDPOINTS.setWorkspace,
|
|
7579
|
+
{ botId: account.botId, workspace }
|
|
7580
|
+
),
|
|
7170
7581
|
onRequestRemove: () => setRemoveTarget(account.botId),
|
|
7171
7582
|
onCancelRemove: () => setRemoveTarget(null),
|
|
7172
7583
|
onConfirmRemove: async () => {
|
|
@@ -7201,7 +7612,7 @@ function WhatsappSettingsTab({ rpcCall }) {
|
|
|
7201
7612
|
h2(Button13, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
|
|
7202
7613
|
)
|
|
7203
7614
|
) : h2(
|
|
7204
|
-
|
|
7615
|
+
React14.Fragment,
|
|
7205
7616
|
null,
|
|
7206
7617
|
provision?.status === "pending" ? h2(QrPanel5, {
|
|
7207
7618
|
provision,
|
|
@@ -7355,6 +7766,19 @@ var CSS10 = String.raw`
|
|
|
7355
7766
|
.dim-panel .dim-confirm strong, .dim-panel .dim-confirm h4 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 1.4; font-weight: 650; }
|
|
7356
7767
|
.dim-panel .dim-confirm p { margin: 7px 0 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; }
|
|
7357
7768
|
.dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
7769
|
+
.dim-panel .dim-workspace { min-width: 0; display: grid; gap: 7px; margin-top: 14px; padding: 11px 12px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
7770
|
+
.dim-panel .dim-workspaceHeader { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; }
|
|
7771
|
+
.dim-panel .dim-workspaceEdit { padding: 0; border: 0; color: #1677ff; background: transparent; font: inherit; font-weight: 560; cursor: pointer; }
|
|
7772
|
+
.dim-panel .dim-workspaceEdit:disabled { cursor: not-allowed; opacity: .55; }
|
|
7773
|
+
.dim-panel .dim-workspacePath { min-width: 0; display: block; overflow: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
|
7774
|
+
.dim-panel .dim-workspaceForm { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; }
|
|
7775
|
+
.dim-panel .dim-workspaceForm input { width: 100%; min-width: 0; height: 34px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
7776
|
+
.dim-panel .dim-workspaceForm input:focus { border-color: #1677ff; box-shadow: 0 0 0 2px rgb(22 119 255 / 10%); }
|
|
7777
|
+
.dim-panel .dim-workspaceActions { display: flex; gap: 6px; }
|
|
7778
|
+
.dim-panel .dim-workspaceActions button { min-height: 34px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; cursor: pointer; }
|
|
7779
|
+
.dim-panel .dim-workspaceActions button[type="submit"] { border-color: #1677ff; color: white; background: #1677ff; }
|
|
7780
|
+
.dim-panel .dim-workspaceActions button:disabled { cursor: not-allowed; opacity: .55; }
|
|
7781
|
+
.dim-panel .dim-workspaceError { grid-column: 1 / -1; margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.5; }
|
|
7358
7782
|
.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; }
|
|
7359
7783
|
.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; }
|
|
7360
7784
|
.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
|
|
@@ -7526,8 +7950,8 @@ function IMSettingsTab({
|
|
|
7526
7950
|
weixinRpcCall,
|
|
7527
7951
|
whatsappRpcCall
|
|
7528
7952
|
}) {
|
|
7529
|
-
const [selected, setSelected] =
|
|
7530
|
-
const githubTooltipId =
|
|
7953
|
+
const [selected, setSelected] = React15.useState("weixin");
|
|
7954
|
+
const githubTooltipId = React15.useId();
|
|
7531
7955
|
const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0];
|
|
7532
7956
|
return h2(
|
|
7533
7957
|
"section",
|