From 828a50398a928e7c31a5a80d9c3e19f63e52a19f Mon Sep 17 00:00:00 2001 From: xmanrui <841206367@qq.com> Date: Sat, 15 Aug 2026 16:24:20 +0800 Subject: [PATCH] Refine unified IM settings UI --- lib/client.js | 1260 ++++++++--------- plugin-src/client/channel-logos.js | 46 + plugin-src/client/channels/dingtalk/index.js | 6 +- plugin-src/client/channels/dingtalk/styles.js | 3 +- plugin-src/client/channels/feishu/index.js | 30 +- plugin-src/client/channels/feishu/styles.js | 21 +- plugin-src/client/channels/weixin/index.js | 32 +- plugin-src/client/channels/weixin/styles.js | 2 +- plugin-src/client/index.js | 22 +- plugin-src/client/styles.js | 3 +- test/channels/dingtalk/client-ui.test.mjs | 6 +- test/client-ui.test.mjs | 106 ++ 12 files changed, 797 insertions(+), 740 deletions(-) create mode 100644 plugin-src/client/channel-logos.js diff --git a/lib/client.js b/lib/client.js index ca7c2be..9bc4b15 100644 --- a/lib/client.js +++ b/lib/client.js @@ -40,7 +40,53 @@ __export(index_exports, { name: () => name }); module.exports = __toCommonJS(index_exports); -var React5 = __toESM(require("react"), 1); +var React6 = __toESM(require("react"), 1); + +// plugin-src/client/channel-logos.js +var React = __toESM(require("react"), 1); +var h = React.createElement; +function dimensions(size) { + return size === void 0 ? {} : { width: size, height: size }; +} +function WeixinLogoGlyph({ size } = {}) { + return h("svg", { + ...dimensions(size), + viewBox: "0 0 24 24", + focusable: "false", + "aria-hidden": "true", + "data-im-channel-logo": "weixin" + }, h("path", { + fill: "currentColor", + d: "M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z" + })); +} +function FeishuLogoGlyph({ size } = {}) { + return h( + "svg", + { + ...dimensions(size), + viewBox: "0 0 24 24", + focusable: "false", + "aria-hidden": "true", + "data-im-channel-logo": "feishu" + }, + h("path", { fill: "#00D6B9", d: "M7.2 4.5h7.6c1.2 0 2.1.55 2.7 1.58 1.05 1.8 1.55 3.45 1.58 4.95-2.04-.62-4.2-.15-6.22 1.45C11.3 9.7 9.42 7.04 7.2 4.5Z" }), + h("path", { fill: "#1456B8", d: "M10.8 13.55c3.3-2.93 5.72-4.24 9.47-2.52-1.2 1.45-2.27 4.18-3.86 5.43-1.67 1.31-3.9.5-5.61-.64v-2.27Z" }), + h("path", { fill: "#3370FF", d: "M4.4 8.35c3.47 3.61 7.25 6.1 10.33 5.7 1.06-.14 2.2-.72 3.4-1.72-1.04 2.65-2.6 4.8-5.06 6-2.46 1.2-5.56.52-7.42-.72A2.76 2.76 0 0 1 4.4 15.3V8.35Z" }) + ); +} +function DingtalkLogoGlyph({ size } = {}) { + return h("svg", { + ...dimensions(size), + viewBox: "0 0 48 48", + focusable: "false", + "aria-hidden": "true", + "data-im-channel-logo": "dingtalk" + }, h("path", { + fill: "currentColor", + 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" + })); +} // plugin-src/client/channels/dingtalk/api.js var DINGTALK_RPC_CHANNEL = "/dingtalk"; @@ -222,7 +268,7 @@ function formatRemaining(milliseconds) { } // plugin-src/client/channels/dingtalk/index.js -var React = __toESM(require("react"), 1); +var React2 = __toESM(require("react"), 1); // plugin-src/client/channels/dingtalk/styles.js var DINGTALK_STYLE_ID = "xmanrui-dsh-dingtalk-settings"; @@ -294,7 +340,6 @@ var CSS = String.raw` .ddt-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: ddt-step; } .ddt-steps li { position: relative; min-height: 28px; padding: 3px 0 3px 36px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 22px; counter-increment: ddt-step; } .ddt-steps li::before { content: counter(ddt-step); position: absolute; left: 0; top: 1px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px; color: var(--ddt-accent-deep); background: var(--ddt-accent-wash); font-size: 12px; font-weight: 700; } -.ddt-brandNotice { margin: 0 0 18px; padding: 10px 12px; border-left: 3px solid #91caff; border-radius: 0 8px 8px 0; color: var(--dsw-alias-label-secondary, #646a73); background: #f5f9ff; font-size: 12px; line-height: 1.55; } .ddt-loading { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } .ddt-loading h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; } .ddt-loading p { line-height: 1.6; } @@ -314,7 +359,7 @@ var CSS = String.raw` .ddt-accountIdentity h3 { overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; } .ddt-accountIdentity p { margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; } .ddt-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; } -.ddt-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } +.ddt-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } .ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } @@ -368,10 +413,10 @@ function installDingtalkStyles() { } // plugin-src/client/channels/dingtalk/index.js -var h = React.createElement; +var h2 = React2.createElement; var ACTIVE_PROVISION_STATES = /* @__PURE__ */ new Set(["pending", "scanned", "authorizing", "creating", "connecting"]); function DingtalkIcon({ size = 28 }) { - return h("svg", { + return h2("svg", { width: size, height: size, viewBox: "0 0 48 48", @@ -379,13 +424,13 @@ function DingtalkIcon({ size = 28 }) { xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false" - }, h("path", { + }, h2("path", { fill: "currentColor", 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" })); } -var Button = React.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) { - return h("button", { +var Button = React2.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) { + return h2("button", { ...props, ref, type: "button", @@ -394,33 +439,33 @@ var Button = React.forwardRef(function Button2({ children, kind = "secondary", c }, children); }); function Heading({ totals, adding, busy, onAdd, addButtonRef }) { - return h( + return h2( "div", { className: "ddt-heading" }, - h( + h2( "div", { className: "ddt-headingCopy" }, - h("div", { className: "ddt-eyebrow" }, "Channel"), - h("h2", null, "\u9489\u9489\u673A\u5668\u4EBA"), - h("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness") + h2("div", { className: "ddt-eyebrow" }, "Channel"), + h2("h2", null, "\u9489\u9489\u673A\u5668\u4EBA"), + h2("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness") ), - h( + h2( "div", { className: "ddt-tools" }, - totals.configured > 0 ? h( + totals.configured > 0 ? h2( "div", { className: "ddt-badge" }, - h("span", { + h2("span", { className: "ddt-dot", "data-tone": totals.connected > 0 ? "success" : "warning" }), - h("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) + h2("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) ) : null, - h("div", { + h2("div", { className: "ddt-badge", title: "\u5E94\u7528\u5BC6\u94A5\u53EA\u5199\u5165 Harness Host \u51ED\u636E\u670D\u52A1\uFF0C\u4E0D\u4F1A\u53D1\u9001\u5230\u6D4F\u89C8\u5668" }, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A"), - h(Button, { + h2(Button, { kind: "primary", onClick: onAdd, disabled: adding || busy, @@ -430,121 +475,116 @@ function Heading({ totals, adding, busy, onAdd, addButtonRef }) { ); } function LoadingView() { - return h( + return h2( "div", { className: "ddt-card ddt-loading", "aria-busy": "true" }, - h("div", { className: "ddt-spinner" }), - h("span", null, "\u6B63\u5728\u8BFB\u53D6\u9489\u9489\u8FDE\u63A5\u72B6\u6001\u2026") + h2("div", { className: "ddt-spinner" }), + h2("span", null, "\u6B63\u5728\u8BFB\u53D6\u9489\u9489\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView({ busy, onStart }) { - return h( + return h2( "div", { className: "ddt-card" }, - h( + h2( "div", { className: "ddt-cardBody ddt-empty" }, - h( + h2( "div", null, - h( + h2( "div", { className: "ddt-stateLabel" }, - h("span", { className: "ddt-dot" }), - h("span", null, "\u5C1A\u672A\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA") + h2("span", { className: "ddt-dot" }), + h2("span", null, "\u5C1A\u672A\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA") ), - h("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u81EA\u52A8\u521B\u5EFA\u5E76\u8FDE\u63A5\u673A\u5668\u4EBA"), - h("p", null, "\u6388\u6743\u7531\u9489\u9489\u5B98\u65B9\u9875\u9762\u5B8C\u6210\u3002\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4E00\u4E2A\u4F01\u4E1A/\u7EC4\u7EC7\u5E76\u6709\u6743\u521B\u5EFA\u673A\u5668\u4EBA\uFF1B\u521B\u5EFA\u6210\u529F\u540E\uFF0C\u5E94\u7528\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\u3002"), - h( + h2("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u81EA\u52A8\u521B\u5EFA\u5E76\u8FDE\u63A5\u673A\u5668\u4EBA"), + h2("p", null, "\u6388\u6743\u7531\u9489\u9489\u5B98\u65B9\u9875\u9762\u5B8C\u6210\u3002\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4E00\u4E2A\u4F01\u4E1A/\u7EC4\u7EC7\u5E76\u6709\u6743\u521B\u5EFA\u673A\u5668\u4EBA\uFF1B\u521B\u5EFA\u6210\u529F\u540E\uFF0C\u5E94\u7528\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\u3002"), + h2( "div", { className: "ddt-actions" }, - h( + h2( Button, { kind: "primary", onClick: onStart, disabled: busy }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u9489\u9489\u4E8C\u7EF4\u7801" ) ) ), - h( + h2( "div", { className: "ddt-brandMark", "aria-hidden": "true" }, - h(DingtalkIcon, { size: 68 }) + h2(DingtalkIcon, { size: 68 }) ) ) ); } function QrPanel({ provision, now, busy, onRefresh, onCancel }) { - const [imageFailed, setImageFailed] = React.useState(false); + const [imageFailed, setImageFailed] = React2.useState(false); const source = safeQrSource(provision.qrCodeDataUrl); const remaining = Math.max(0, provision.expiresAt - now); const expired = remaining === 0 || provision.status === "expired"; const duration = Math.max(1, provision.durationMs ?? 10 * 6e4); const progress = Math.round(Math.min(1, remaining / duration) * 100); - React.useEffect(() => setImageFailed(false), [source]); - return h( + React2.useEffect(() => setImageFailed(false), [source]); + return h2( "div", { className: "ddt-card" }, - h( + h2( "div", { className: "ddt-cardBody ddt-qrLayout" }, - h( + h2( "div", { className: "ddt-qrColumn" }, - h( + h2( "div", { className: "ddt-qrFrame" }, - source && !imageFailed ? h("img", { + source && !imageFailed ? h2("img", { src: source, alt: "\u7528\u4E8E\u628A\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness \u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) - }) : h("div", { className: "ddt-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"), - expired ? h("div", { className: "ddt-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null + }) : h2("div", { className: "ddt-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"), + expired ? h2("div", { className: "ddt-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), - h( + h2( "div", { className: "ddt-countdown" }, - h( + h2( "div", { className: "ddt-countdownTop" }, - h("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), - h("strong", null, formatRemaining(remaining)) + h2("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), + h2("strong", null, formatRemaining(remaining)) ), - h( + h2( "div", { className: "ddt-progress", "aria-hidden": "true" }, - h("span", { style: { "--ddt-progress": `${progress}%` } }) + h2("span", { style: { "--ddt-progress": `${progress}%` } }) ) ) ), - h( + h2( "div", { className: "ddt-qrCopy" }, - h( + h2( "div", { className: "ddt-stateLabel" }, - h("span", { className: "ddt-dot", "data-tone": expired ? "error" : "warning" }), - h("span", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u7B49\u5F85\u9489\u9489\u626B\u7801\u6388\u6743") + h2("span", { className: "ddt-dot", "data-tone": expired ? "error" : "warning" }), + h2("span", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u7B49\u5F85\u9489\u9489\u626B\u7801\u6388\u6743") ), - h("h3", null, expired ? "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u9489\u9489 App \u5B8C\u6210\u673A\u5668\u4EBA\u6388\u6743"), - h("p", null, "\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u3002\u5982\u679C\u9489\u9489\u63D0\u793A\u5C1A\u672A\u52A0\u5165\u7EC4\u7EC7\uFF0C\u8BF7\u5728\u63D0\u793A\u9875\u521B\u5EFA\u7EC4\u7EC7\uFF0C\u6216\u6362\u7528\u5DF2\u52A0\u5165\u7EC4\u7EC7\u7684\u8D26\u53F7\u3002"), - h( + h2("h3", null, expired ? "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u9489\u9489 App \u5B8C\u6210\u673A\u5668\u4EBA\u6388\u6743"), + h2("p", null, "\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u3002\u5982\u679C\u9489\u9489\u63D0\u793A\u5C1A\u672A\u52A0\u5165\u7EC4\u7EC7\uFF0C\u8BF7\u5728\u63D0\u793A\u9875\u521B\u5EFA\u7EC4\u7EC7\uFF0C\u6216\u6362\u7528\u5DF2\u52A0\u5165\u7EC4\u7EC7\u7684\u8D26\u53F7\u3002"), + h2( "ol", { className: "ddt-steps" }, - h("li", null, "\u4F7F\u7528\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u7684\u9489\u9489\u8D26\u53F7\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), - h("li", null, "\u5728\u6388\u6743\u9875\u70B9\u51FB\u201C\u4E00\u952E\u521B\u5EFA\u65B0\u673A\u5668\u4EBA\u201D"), - h("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u673A\u5668\u4EBA\u81EA\u52A8\u8FDE\u63A5") + h2("li", null, "\u4F7F\u7528\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u7684\u9489\u9489\u8D26\u53F7\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), + h2("li", null, "\u5728\u6388\u6743\u9875\u70B9\u51FB\u201C\u4E00\u952E\u521B\u5EFA\u65B0\u673A\u5668\u4EBA\u201D"), + h2("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u673A\u5668\u4EBA\u81EA\u52A8\u8FDE\u63A5") ), - h( - "div", - { className: "ddt-brandNotice" }, - "\u9489\u9489\u5B98\u65B9\u6388\u6743\u9875\u76EE\u524D\u53EF\u80FD\u663E\u793A OpenClaw \u54C1\u724C\uFF0C\u8FD9\u662F\u5B98\u65B9\u8FDE\u63A5\u5668\u6388\u6743\u9875\u9762\uFF0C\u4E0D\u5F71\u54CD\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness\u3002" - ), - h( + h2( "div", { className: "ddt-actions" }, - expired ? h(Button, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, - !expired ? h(Button, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, - h(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") + expired ? h2(Button, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, + !expired ? h2(Button, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, + h2(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") ) ) ) @@ -553,16 +593,16 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { function ProgressPanel({ status, busy, onCancel }) { const connecting = status === "connecting"; const creating = status === "creating"; - return h( + return h2( "div", { className: "ddt-card ddt-loading", "aria-busy": "true" }, - h("div", { className: "ddt-spinner" }), - h("h3", null, connecting ? "\u673A\u5668\u4EBA\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u6D88\u606F\u8FDE\u63A5" : creating ? "\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u521B\u5EFA\u9489\u9489\u673A\u5668\u4EBA" : "\u6B63\u5728\u786E\u8BA4\u9489\u9489\u6388\u6743"), - h("p", null, connecting ? "\u6B63\u5728\u68C0\u67E5\u9489\u9489 Stream \u957F\u8FDE\u63A5\uFF0C\u6210\u529F\u540E\u4F1A\u81EA\u52A8\u663E\u793A\u4E3A\u5728\u7EBF\u3002" : "\u8BF7\u52FF\u5173\u95ED\u672C\u9875\uFF0C\u9489\u9489\u5B8C\u6210\u6388\u6743\u540E\u5C06\u81EA\u52A8\u7EE7\u7EED\u3002"), - h( + h2("div", { className: "ddt-spinner" }), + h2("h3", null, connecting ? "\u673A\u5668\u4EBA\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u6D88\u606F\u8FDE\u63A5" : creating ? "\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u521B\u5EFA\u9489\u9489\u673A\u5668\u4EBA" : "\u6B63\u5728\u786E\u8BA4\u9489\u9489\u6388\u6743"), + h2("p", null, connecting ? "\u6B63\u5728\u68C0\u67E5\u9489\u9489 Stream \u957F\u8FDE\u63A5\uFF0C\u6210\u529F\u540E\u4F1A\u81EA\u52A8\u663E\u793A\u4E3A\u5728\u7EBF\u3002" : "\u8BF7\u52FF\u5173\u95ED\u672C\u9875\uFF0C\u9489\u9489\u5B8C\u6210\u6388\u6743\u540E\u5C06\u81EA\u52A8\u7EE7\u7EED\u3002"), + h2( "div", { className: "ddt-actions", style: { justifyContent: "center", marginTop: 14 } }, - h(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u63A5\u5165") + h2(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u63A5\u5165") ) ); } @@ -571,20 +611,20 @@ function ProvisionError({ provision, busy, onRetry, onClose }) { code: "DINGTALK_PROVISION_FAILED", message: "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210" }; - return h( + return h2( "div", { className: "ddt-card" }, - h( + h2( "div", { className: "ddt-inlineError", role: "alert" }, - h("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"), - h("p", null, error.message), - h("span", { className: "ddt-errorCode" }, error.code), - h( + h2("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"), + h2("p", null, error.message), + h2("span", { className: "ddt-errorCode" }, error.code), + h2( "div", { className: "ddt-actions" }, - h(Button, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), - h(Button, { onClick: onClose, disabled: busy }, "\u5173\u95ED") + h2(Button, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), + h2(Button, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) ) ); @@ -602,9 +642,9 @@ function checkedTime(value) { } } function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { - const cancelRef = React.useRef(null); - React.useEffect(() => cancelRef.current?.focus(), []); - return h( + const cancelRef = React2.useRef(null); + React2.useEffect(() => cancelRef.current?.focus(), []); + return h2( "div", { className: "ddt-confirm", @@ -614,13 +654,13 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { if (event.key === "Escape" && !busy) onCancel(); } }, - h("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`), - h("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5E94\u7528\u51ED\u636E\u3001\u673A\u5668\u4EBA\u914D\u7F6E\u53CA\u4F1A\u8BDD\u6620\u5C04\u3002\u9489\u9489\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002"), - h( + h2("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`), + h2("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5E94\u7528\u51ED\u636E\u3001\u673A\u5668\u4EBA\u914D\u7F6E\u53CA\u4F1A\u8BDD\u6620\u5C04\u3002\u9489\u9489\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002"), + h2( "div", { className: "ddt-actions" }, - h(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), - h( + h2(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), + h2( Button, { kind: "danger", onClick: onConfirm, disabled: busy }, busy ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165" @@ -640,68 +680,62 @@ function AccountCard({ const state = busy === "reconnect" ? "connecting" : account.state; const tone = account.connected ? "success" : state === "error" ? "error" : "warning"; const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA"; - return h( + return h2( "article", { className: "ddt-card", tabIndex: -1, "data-bot-id": account.botId }, - h( + h2( "div", { className: "ddt-cardBody" }, - h( + h2( "div", { className: "ddt-accountTop" }, - h( + h2( "div", { className: "ddt-accountIdentity" }, - h("div", { className: "ddt-avatar", "aria-hidden": "true" }, h(DingtalkIcon, { size: 29 })), - h( + h2("div", { className: "ddt-avatar", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 29 })), + h2( "div", null, - h("h3", { title: account.bot.name }, account.bot.name), - h("p", { title: account.bot.clientIdMasked }, account.bot.clientIdMasked) + h2("h3", { title: account.bot.name }, account.bot.name), + h2("p", { title: account.bot.clientIdMasked }, account.bot.clientIdMasked) ) ), - h( + h2( "div", { className: "ddt-health" }, - h("span", { className: "ddt-dot", "data-tone": tone }), - h("span", null, stateLabel) + h2("span", { className: "ddt-dot", "data-tone": tone }), + h2("span", null, stateLabel) ) ), - h( + h2( "dl", { className: "ddt-metrics" }, - h( + h2( "div", { className: "ddt-metric" }, - h("dt", null, "\u6D88\u606F\u901A\u9053"), - h("dd", null, account.connected ? "Stream \u957F\u8FDE\u63A5" : "\u79BB\u7EBF") + h2("dt", null, "\u6D88\u606F\u901A\u9053"), + h2("dd", null, account.connected ? "Stream \u957F\u8FDE\u63A5" : "\u79BB\u7EBF") ), - h( + h2( "div", { className: "ddt-metric" }, - h("dt", null, "\u6536\u5230 / \u56DE\u590D"), - h("dd", null, `${account.stats.messagesReceived} / ${account.stats.messagesReplied}`) - ), - h( - "div", - { className: "ddt-metric" }, - h("dt", null, "\u6700\u8FD1\u68C0\u67E5"), - h("dd", null, checkedTime(account.health.lastCheckedAt)) + h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), + h2("dd", null, checkedTime(account.health.lastCheckedAt)) ) ), - h( + h2( "div", { className: "ddt-accountFooter" }, - h("div", { className: "ddt-summary" }, account.error?.message ?? account.health.summary), - h( + h2("div", { className: "ddt-summary" }, account.error?.message ?? account.health.summary), + h2( "div", { className: "ddt-actions" }, - h( + h2( Button, { onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), - h( + h2( Button, { kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165" @@ -709,7 +743,7 @@ function AccountCard({ ) ) ), - removing ? h(RemoveConfirmation, { + removing ? h2(RemoveConfirmation, { account, busy: busy === "delete", onConfirm: onConfirmRemove, @@ -718,19 +752,19 @@ function AccountCard({ ); } function AccountList(props) { - return h( + return h2( "section", null, - h( + h2( "div", { className: "ddt-listHeading" }, - h("h3", null, "\u5DF2\u63A5\u5165\u7684\u9489\u9489\u673A\u5668\u4EBA"), - h("span", null, `${props.bots.length} \u4E2A`) + h2("h3", null, "\u5DF2\u63A5\u5165\u7684\u9489\u9489\u673A\u5668\u4EBA"), + h2("span", null, `${props.bots.length} \u4E2A`) ), - h("ul", { className: "ddt-list" }, props.bots.map((account) => h( + h2("ul", { className: "ddt-list" }, props.bots.map((account) => h2( "li", { key: account.botId }, - h(AccountCard, { + h2(AccountCard, { account, busy: props.busyByBot[account.botId], removing: props.removeTarget === account.botId, @@ -744,25 +778,25 @@ function AccountList(props) { } var EMPTY_TOTALS = Object.freeze({ configured: 0, connected: 0 }); function DingtalkSettingsTab({ rpcCall }) { - const [model, setModel] = React.useState({ + const [model, setModel] = React2.useState({ phase: "loading", bots: [], totals: EMPTY_TOTALS, revision: 0, error: null }); - const [provision, setProvision] = React.useState(null); - const [busy, setBusy] = React.useState(false); - const [busyByBot, setBusyByBot] = React.useState({}); - const [removeTarget, setRemoveTarget] = React.useState(null); - const [notice, setNotice] = React.useState(""); - const [now, setNow] = React.useState(() => Date.now()); - const addButtonRef = React.useRef(null); - const mountedRef = React.useRef(true); - const statusRequestRef = React.useRef(0); - const noticeFrameRef = React.useRef(null); - const focusFrameRef = React.useRef(null); - React.useEffect(() => { + const [provision, setProvision] = React2.useState(null); + const [busy, setBusy] = React2.useState(false); + const [busyByBot, setBusyByBot] = React2.useState({}); + const [removeTarget, setRemoveTarget] = React2.useState(null); + const [notice, setNotice] = React2.useState(""); + const [now, setNow] = React2.useState(() => Date.now()); + const addButtonRef = React2.useRef(null); + const mountedRef = React2.useRef(true); + const statusRequestRef = React2.useRef(0); + const noticeFrameRef = React2.useRef(null); + const focusFrameRef = React2.useRef(null); + React2.useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; @@ -777,8 +811,8 @@ function DingtalkSettingsTab({ rpcCall }) { } }; }, []); - React.useEffect(() => installDingtalkStyles(), []); - const announce = React.useCallback((message) => { + React2.useEffect(() => installDingtalkStyles(), []); + const announce = React2.useCallback((message) => { if (!mountedRef.current) return; if (noticeFrameRef.current !== null) { window.cancelAnimationFrame(noticeFrameRef.current); @@ -792,7 +826,7 @@ function DingtalkSettingsTab({ rpcCall }) { }); } }, []); - const focusAddButton = React.useCallback(() => { + const focusAddButton = React2.useCallback(() => { if (!mountedRef.current) return; if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current); focusFrameRef.current = window.requestAnimationFrame(() => { @@ -800,11 +834,11 @@ function DingtalkSettingsTab({ rpcCall }) { if (mountedRef.current) addButtonRef.current?.focus(); }); }, []); - const invoke = React.useCallback(async (endpoint, payload = {}, signal) => { + const invoke = React2.useCallback(async (endpoint, payload = {}, signal) => { if (typeof rpcCall !== "function") throw new TypeError("\u9489\u9489\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5"); return unwrapRpcResult(await rpcCall(endpoint, payload, signal)); }, [rpcCall]); - const loadStatus = React.useCallback(async ({ + const loadStatus = React2.useCallback(async ({ signal, silent = false, restoreProvisioning = false @@ -844,12 +878,12 @@ function DingtalkSettingsTab({ rpcCall }) { return void 0; } }, [invoke]); - React.useEffect(() => { + React2.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); - React.useEffect(() => { + React2.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let running = false; @@ -868,14 +902,14 @@ function DingtalkSettingsTab({ rpcCall }) { window.clearInterval(timer); }; }, [loadStatus, model.phase]); - React.useEffect(() => { + React2.useEffect(() => { if (!provision || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0; const timer = window.setInterval(() => { if (mountedRef.current) setNow(Date.now()); }, 1e3); return () => window.clearInterval(timer); }, [provision?.attemptId, provision?.status]); - const startProvisioning = React.useCallback(async ({ replace = false } = {}) => { + const startProvisioning = React2.useCallback(async ({ replace = false } = {}) => { if (!mountedRef.current) return; setBusy(true); try { @@ -911,7 +945,7 @@ function DingtalkSettingsTab({ rpcCall }) { if (mountedRef.current) setBusy(false); } }, [announce, invoke, provision?.attemptId]); - const cancelProvisioning = React.useCallback(async () => { + const cancelProvisioning = React2.useCallback(async () => { if (!mountedRef.current) return; setBusy(true); try { @@ -929,7 +963,7 @@ function DingtalkSettingsTab({ rpcCall }) { if (mountedRef.current) setBusy(false); } }, [announce, focusAddButton, invoke, provision?.attemptId, provision?.status]); - React.useEffect(() => { + React2.useEffect(() => { const attemptId = provision?.attemptId; if (!attemptId || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0; const controller = new AbortController(); @@ -988,7 +1022,7 @@ function DingtalkSettingsTab({ rpcCall }) { timer = null; }; }, [announce, invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]); - const setBotBusy = React.useCallback((botId, operation) => { + const setBotBusy = React2.useCallback((botId, operation) => { if (!mountedRef.current) return; setBusyByBot((current) => { const next = { ...current }; @@ -997,7 +1031,7 @@ function DingtalkSettingsTab({ rpcCall }) { return next; }); }, []); - const runBotAction = React.useCallback(async ({ account, operation, endpoint, payload, success }) => { + const runBotAction = React2.useCallback(async ({ account, operation, endpoint, payload, success }) => { if (!mountedRef.current) return void 0; setBotBusy(account.botId, operation); try { @@ -1015,14 +1049,14 @@ function DingtalkSettingsTab({ rpcCall }) { if (mountedRef.current) setBotBusy(account.botId, null); } }, [announce, invoke, loadStatus, setBotBusy]); - const reconnect = React.useCallback((account) => runBotAction({ + const reconnect = React2.useCallback((account) => runBotAction({ account, operation: "reconnect", endpoint: DINGTALK_ENDPOINTS.reconnectBot, payload: { botId: account.botId }, 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" }), [runBotAction]); - const remove = React.useCallback(async (account) => { + const remove = React2.useCallback(async (account) => { const snapshot = await runBotAction({ account, operation: "delete", @@ -1034,14 +1068,14 @@ function DingtalkSettingsTab({ rpcCall }) { }, [runBotAction]); let provisionView = null; if (provision?.status === "starting") { - provisionView = h( + provisionView = h2( "div", { className: "ddt-card ddt-loading", "aria-busy": "true" }, - h("div", { className: "ddt-spinner" }), - h("span", null, "\u6B63\u5728\u7533\u8BF7\u9489\u9489\u6388\u6743\u4E8C\u7EF4\u7801\u2026") + h2("div", { className: "ddt-spinner" }), + h2("span", null, "\u6B63\u5728\u7533\u8BF7\u9489\u9489\u6388\u6743\u4E8C\u7EF4\u7801\u2026") ); } else if (provision?.status === "pending") { - provisionView = h(QrPanel, { + provisionView = h2(QrPanel, { provision, now, busy, @@ -1049,47 +1083,47 @@ function DingtalkSettingsTab({ rpcCall }) { onCancel: () => void cancelProvisioning() }); } else if (["scanned", "authorizing", "creating", "connecting"].includes(provision?.status)) { - provisionView = h(ProgressPanel, { + provisionView = h2(ProgressPanel, { status: provision.status, busy, onCancel: () => void cancelProvisioning() }); } else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) { - provisionView = h(ProvisionError, { + provisionView = h2(ProvisionError, { provision, busy, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onClose: () => void cancelProvisioning() }); } - return h( + return h2( "section", { className: "ddt-page", "aria-label": "\u9489\u9489\u8BBE\u7F6E" }, - h(Heading, { + h2(Heading, { totals: model.totals, adding: Boolean(provision), busy, onAdd: () => void startProvisioning(), addButtonRef }), - h("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice), - model.error && model.phase === "ready" ? h("div", { className: "ddt-statusNotice", role: "alert" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, - model.phase === "loading" ? h(LoadingView) : model.phase === "error" ? h( + h2("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice), + model.error && model.phase === "ready" ? h2("div", { className: "ddt-statusNotice", role: "alert" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, + model.phase === "loading" ? h2(LoadingView) : model.phase === "error" ? h2( "div", { className: "ddt-card" }, - h( + h2( "div", { className: "ddt-inlineError", role: "alert" }, - h("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u9489\u9489\u673A\u5668\u4EBA\u72B6\u6001"), - h("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), - h(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") + h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u9489\u9489\u673A\u5668\u4EBA\u72B6\u6001"), + h2("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), + h2(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") ) - ) : h( - React.Fragment, + ) : h2( + React2.Fragment, null, provisionView, - model.bots.length === 0 && !provision ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null, - model.bots.length > 0 ? h(AccountList, { + model.bots.length === 0 && !provision ? h2(EmptyView, { busy, onStart: () => void startProvisioning() }) : null, + model.bots.length > 0 ? h2(AccountList, { bots: model.bots, busyByBot, removeTarget, @@ -1103,7 +1137,7 @@ function DingtalkSettingsTab({ rpcCall }) { } // plugin-src/client/channels/feishu/index.js -var React3 = __toESM(require("react"), 1); +var React4 = __toESM(require("react"), 1); // plugin-src/client/channels/feishu/api.js var FEISHU_RPC_CHANNEL = "/feishu"; @@ -1320,7 +1354,7 @@ function formatRemaining2(milliseconds) { } // plugin-src/client/lifecycle.js -var React2 = __toESM(require("react"), 1); +var React3 = __toESM(require("react"), 1); function createPollScheduler({ setTimeoutFn, clearTimeoutFn }) { let disposed = false; let timer; @@ -1382,8 +1416,8 @@ function createAnimationFrameScheduler({ requestFrame, cancelFrame }) { }; } function useAnimationFrameScheduler() { - const schedulerRef = React2.useRef(null); - React2.useEffect(() => { + const schedulerRef = React3.useRef(null); + React3.useEffect(() => { const scheduler = createAnimationFrameScheduler({ requestFrame: (callback) => window.requestAnimationFrame(callback), cancelFrame: (frame) => window.cancelAnimationFrame(frame) @@ -1394,7 +1428,7 @@ function useAnimationFrameScheduler() { if (schedulerRef.current === scheduler) schedulerRef.current = null; }; }, []); - return React2.useCallback( + return React3.useCallback( (callback, key) => schedulerRef.current?.schedule(callback, key) ?? false, [] ); @@ -1458,11 +1492,12 @@ var CSS2 = String.raw` } .bxf-headingTools { + width: 100%; flex: none; display: flex; align-items: center; justify-content: flex-end; - flex-wrap: wrap; + flex-wrap: nowrap; gap: 8px; } @@ -1648,6 +1683,7 @@ var CSS2 = String.raw` .bxf-button[data-kind="danger"] { color: var(--bxf-error); } .bxf-button[data-size="small"] { min-height: 32px; padding: 5px 10px; font-size: 12px; } +.bxf-bindButton { flex: none; white-space: nowrap; } .bxf-provisionCard { border-color: color-mix(in srgb, var(--bxf-accent) 32%, var(--dsw-alias-border-l2, #dee0e3)); @@ -1810,17 +1846,16 @@ var CSS2 = String.raw` .bxf-connectedTop { display: flex; align-items: center; justify-content: space-between; gap: 20px; } .bxf-botIdentity { min-width: 0; display: flex; align-items: center; gap: 13px; } -.bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border-radius: 14px; color: var(--bxf-accent); background: color-mix(in srgb, var(--bxf-accent) 9%, var(--dsw-alias-bg-layer-1, #fff)); } -.bxf-avatar img { width: 100%; height: 100%; object-fit: cover; } +.bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 3px rgb(31 35 41 / 7%); } .bxf-botName { min-width: 0; } .bxf-botName h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 17px; line-height: 24px; font-weight: 650; } -.bxf-botName p { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: 18px; margin-top: 2px; } +.bxf-botName p { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-family: var(--ds-font-family-code, monospace); font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; } .bxf-healthPill { flex: none; display: inline-flex; align-items: center; gap: 7px; min-height: 28px; border-radius: 999px; padding: 4px 10px; color: var(--bxf-success); background: color-mix(in srgb, var(--bxf-success) 10%, transparent); font-size: 12px; font-weight: 600; line-height: 18px; } .bxf-healthPill[data-health="degraded"], .bxf-healthPill[data-health="checking"], .bxf-healthPill[data-health="connecting"] { color: var(--bxf-warning); background: color-mix(in srgb, var(--bxf-warning) 10%, transparent); } .bxf-healthPill[data-health="offline"], .bxf-healthPill[data-health="error"] { color: var(--bxf-error); background: color-mix(in srgb, var(--bxf-error) 10%, transparent); } -.bxf-statusGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 22px; } +.bxf-statusGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 22px; } .bxf-metric { min-width: 0; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 9px; padding: 12px 13px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); } .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; } .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; } @@ -1878,15 +1913,13 @@ var CSS2 = String.raw` @keyframes bxf-shimmer { to { background-position: -220% 0; } } @container (max-width: 620px) { - .bxf-heading { flex-direction: column; gap: 10px; } - .bxf-headingTools { width: 100%; justify-content: flex-start; } - .bxf-headingTools .bxf-button { margin-left: auto; } + .bxf-headingTools { gap: 6px; } + .bxf-headingTools .bxf-totalBadge, .bxf-headingTools .bxf-localBadge { padding-inline: 8px; } + .bxf-headingTools .bxf-localBadge { gap: 4px; } + .bxf-headingTools .bxf-bindButton { padding-inline: 10px; } } @media (max-width: 680px) { - .bxf-heading { flex-direction: column; gap: 10px; } - .bxf-headingTools { width: 100%; justify-content: flex-start; } - .bxf-headingTools .bxf-button { margin-left: auto; } .bxf-intro { grid-template-columns: minmax(0, 1fr); } .bxf-markStage { display: none; } .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } @@ -1927,9 +1960,9 @@ function installFeishuStyles() { } // plugin-src/client/channels/feishu/index.js -var h2 = React3.createElement; +var h3 = React4.createElement; function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) { - return h2("svg", { + return h3("svg", { width: size, height: size, viewBox, @@ -1941,16 +1974,16 @@ function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) { }, children); } function ShieldIcon({ size = 18 }) { - return h2( + return h3( SvgIcon, { size }, - h2("path", { + h3("path", { d: "M12 3 5.5 5.8v5.1c0 4.25 2.72 7.87 6.5 9.1 3.78-1.23 6.5-4.85 6.5-9.1V5.8L12 3Z", stroke: "currentColor", strokeWidth: "1.7", strokeLinejoin: "round" }), - h2("path", { + h3("path", { d: "m9.3 11.8 1.7 1.7 3.8-4", stroke: "currentColor", strokeWidth: "1.7", @@ -1960,10 +1993,10 @@ function ShieldIcon({ size = 18 }) { ); } function RobotIcon({ size = 26 }) { - return h2( + return h3( SvgIcon, { size }, - h2("rect", { + h3("rect", { x: "5", y: "7.5", width: "14", @@ -1972,7 +2005,7 @@ function RobotIcon({ size = 26 }) { stroke: "currentColor", strokeWidth: "1.7" }), - h2("path", { + h3("path", { d: "M12 4.5v3M8.7 12h.01M15.3 12h.01M9.2 15.3c1.67 1.08 3.93 1.08 5.6 0M3.5 11.5v3M20.5 11.5v3", stroke: "currentColor", strokeWidth: "1.7", @@ -1981,31 +2014,23 @@ function RobotIcon({ size = 26 }) { ); } function SparkIcon({ size = 18 }) { - return h2( + return h3( SvgIcon, { size }, - h2("path", { + h3("path", { d: "M12 2.8c.75 3.67 2.7 5.62 6.4 6.4-3.7.77-5.65 2.72-6.4 6.4-.75-3.68-2.7-5.63-6.4-6.4 3.7-.78 5.65-2.73 6.4-6.4Z", stroke: "currentColor", strokeWidth: "1.55", strokeLinejoin: "round" }), - h2("path", { + h3("path", { d: "M5.2 15.8c.35 1.7 1.28 2.63 3 3-1.72.36-2.65 1.29-3 3-.36-1.71-1.29-2.64-3-3 1.71-.37 2.64-1.3 3-3ZM18.7 2.7c.22 1.06.79 1.63 1.85 1.85-1.06.22-1.63.79-1.85 1.85-.22-1.06-.79-1.63-1.85-1.85 1.06-.22 1.63-.79 1.85-1.85Z", fill: "currentColor" }) ); } -function PlusIcon({ size = 17 }) { - return h2(SvgIcon, { size }, h2("path", { - d: "M12 5v14M5 12h14", - stroke: "currentColor", - strokeWidth: "1.8", - strokeLinecap: "round" - })); -} function RefreshIcon({ size = 16 }) { - return h2(SvgIcon, { size }, h2("path", { + return h3(SvgIcon, { size }, h3("path", { d: "M19 7.5V4m0 0h-3.5M19 4l-2.1 2.1A7 7 0 1 0 19 13", stroke: "currentColor", strokeWidth: "1.8", @@ -2014,7 +2039,7 @@ function RefreshIcon({ size = 16 }) { })); } function ExternalIcon({ size = 15 }) { - return h2(SvgIcon, { size }, h2("path", { + return h3(SvgIcon, { size }, h3("path", { d: "M13 5h6v6M19 5l-8.5 8.5M18 13.5V18a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4.5", stroke: "currentColor", strokeWidth: "1.7", @@ -2023,16 +2048,16 @@ function ExternalIcon({ size = 15 }) { })); } function AlertIcon({ size = 22 }) { - return h2( + return h3( SvgIcon, { size }, - h2("path", { + h3("path", { d: "M12 3.4 21 19H3L12 3.4Z", stroke: "currentColor", strokeWidth: "1.7", strokeLinejoin: "round" }), - h2("path", { + h3("path", { d: "M12 9v4.4M12 16.6v.01", stroke: "currentColor", strokeWidth: "1.9", @@ -2041,108 +2066,104 @@ function AlertIcon({ size = 22 }) { ); } function QrIcon({ size = 58 }) { - return h2(SvgIcon, { size }, h2("path", { + return h3(SvgIcon, { size }, h3("path", { d: "M4 4h6v6H4V4Zm10 0h6v6h-6V4ZM4 14h6v6H4v-6Zm10 0h2v2h-2v-2Zm4 0h2v4h-2v-4Zm-4 4h4v2h-4v-2Z", fill: "currentColor" })); } -var Button3 = React3.forwardRef(function Button4({ children, kind = "secondary", size, icon, className = "", ...props }, ref) { - return h2("button", { +var Button3 = React4.forwardRef(function Button4({ children, kind = "secondary", size, icon, className = "", ...props }, ref) { + return h3("button", { ...props, ref, type: "button", className: `bxf-button ${className}`.trim(), "data-kind": kind, "data-size": size - }, icon, h2("span", null, children)); + }, icon, h3("span", null, children)); }); -function BrandMark({ compact = false }) { - return h2( - "div", - { className: compact ? "bxf-avatar" : "bxf-brandMark" }, - h2(RobotIcon, { size: compact ? 25 : 34 }) - ); +function BrandMark() { + return h3("div", { className: "bxf-brandMark" }, h3(RobotIcon, { size: 34 })); } function Heading2({ totals, onAdd, adding, busy, addButtonRef }) { const hasBots = totals.configured > 0; - return h2( + return h3( "div", { className: "bxf-heading" }, - h2( + h3( "div", { className: "bxf-headingTools" }, - hasBots ? h2("div", { + hasBots ? h3("div", { className: "bxf-totalBadge", "aria-label": `\u5DF2\u63A5\u5165 ${totals.configured} \u4E2A\u673A\u5668\u4EBA\uFF0C\u5176\u4E2D ${totals.connected} \u4E2A\u5728\u7EBF` - }, h2("strong", null, totals.connected), h2("span", null, `/ ${totals.configured} \u5728\u7EBF`)) : null, - h2("div", { + }, h3("strong", null, totals.connected), h3("span", null, `/ ${totals.configured} \u5728\u7EBF`)) : null, + h3("div", { className: "bxf-localBadge", title: "\u6BCF\u4E2A\u5E94\u7528\u7684\u51ED\u636E\u5747\u7531 Host \u72EC\u7ACB\u4FDD\u5B58\uFF0C\u4E0D\u4F1A\u53D1\u9001\u5230\u6D4F\u89C8\u5668" - }, h2(ShieldIcon, { size: 14 }), h2("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A")), - h2(Button3, { + }, h3(ShieldIcon, { size: 14 }), h3("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A")), + h3(Button3, { kind: "primary", size: "small", - icon: h2(PlusIcon), + className: "bxf-bindButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef, "aria-busy": busy ? "true" : void 0 - }, adding ? "\u6B63\u5728\u6DFB\u52A0" : "\u6DFB\u52A0\u673A\u5668\u4EBA") + }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u673A\u5668\u4EBA") ) ); } function LoadingView2() { - return h2("div", { + return h3("div", { className: "bxf-card", "aria-busy": "true", "aria-label": "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u5217\u8868" - }, h2( + }, h3( "div", { className: "bxf-skeleton" }, - h2("div", { className: "bxf-skeletonLine" }), - h2("div", { className: "bxf-skeletonLine" }), - h2("div", { className: "bxf-skeletonLine" }), - h2("div", { className: "bxf-skeletonLine" }), - h2("div", { className: "bxf-skeletonBox" }) + h3("div", { className: "bxf-skeletonLine" }), + h3("div", { className: "bxf-skeletonLine" }), + h3("div", { className: "bxf-skeletonLine" }), + h3("div", { className: "bxf-skeletonLine" }), + h3("div", { className: "bxf-skeletonBox" }) )); } function EmptyView2({ onStart, busy }) { - return h2( + return h3( "div", { className: "bxf-card" }, - h2( + h3( "div", { className: "bxf-cardBody bxf-intro" }, - h2( + h3( "div", { className: "bxf-introCopy" }, - h2( + h3( "div", { className: "bxf-stateLabel" }, - h2("span", { className: "bxf-dot" }), - h2("span", null, "\u5C1A\u672A\u63A5\u5165\u673A\u5668\u4EBA") + h3("span", { className: "bxf-dot" }), + h3("span", null, "\u5C1A\u672A\u63A5\u5165\u673A\u5668\u4EBA") ), - h2("h3", null, "\u626B\u7801\uFF0C\u521B\u5EFA\u7B2C\u4E00\u4E2A\u98DE\u4E66\u5165\u53E3"), - h2("p", null, "\u65E0\u9700\u624B\u52A8\u586B\u5199 App ID\u3002\u4EE5\u540E\u8FD8\u53EF\u4EE5\u7EE7\u7EED\u6DFB\u52A0\u673A\u5668\u4EBA\uFF0C\u5206\u522B\u670D\u52A1\u4E0D\u540C\u56E2\u961F\u6216\u98DE\u4E66\u79DF\u6237\u3002"), - h2( + h3("h3", null, "\u626B\u7801\uFF0C\u521B\u5EFA\u7B2C\u4E00\u4E2A\u98DE\u4E66\u5165\u53E3"), + h3("p", null, "\u65E0\u9700\u624B\u52A8\u586B\u5199 App ID\u3002\u4EE5\u540E\u8FD8\u53EF\u4EE5\u7EE7\u7EED\u6DFB\u52A0\u673A\u5668\u4EBA\uFF0C\u5206\u522B\u670D\u52A1\u4E0D\u540C\u56E2\u961F\u6216\u98DE\u4E66\u79DF\u6237\u3002"), + h3( "div", { className: "bxf-actions" }, - h2(Button3, { + h3(Button3, { kind: "primary", - icon: h2(SparkIcon), + icon: h3(SparkIcon), onClick: onStart, disabled: busy, "aria-busy": busy ? "true" : void 0 }, busy ? "\u6B63\u5728\u521B\u5EFA\u2026" : "\u4E00\u952E\u521B\u5EFA\u98DE\u4E66\u673A\u5668\u4EBA") ), - h2( + h3( "div", { className: "bxf-note" }, - h2(ShieldIcon, { size: 16 }), - h2("span", null, "\u6BCF\u4E2A App Secret \u90FD\u53EA\u5199\u5165 Host \u51ED\u636E\u5B58\u50A8\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 Secret\u3002") + h3(ShieldIcon, { size: 16 }), + h3("span", null, "\u6BCF\u4E2A App Secret \u90FD\u53EA\u5199\u5165 Host \u51ED\u636E\u5B58\u50A8\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 Secret\u3002") ) ), - h2("div", { className: "bxf-markStage", "aria-hidden": "true" }, h2(BrandMark)) + h3("div", { className: "bxf-markStage", "aria-hidden": "true" }, h3(BrandMark)) ) ); } @@ -2160,94 +2181,94 @@ function safeQrSource2(value) { return /^data:image\/(?:png|webp|svg\+xml)(?:;charset=[^;,]+)?;base64,/i.test(value) ? value : void 0; } function QrPane({ provision, now, onRefresh, onCancel, busy }) { - const [imageFailed, setImageFailed] = React3.useState(false); + const [imageFailed, setImageFailed] = React4.useState(false); const qrSource = safeQrSource2(provision.qrCodeDataUrl); const href = safeVerificationHref(provision.verificationUrl); const remaining = Math.max(0, provision.expiresAt - now); const expired = provision.expired === true || remaining === 0; const progress = Math.min(1, remaining / Math.max(1, provision.durationMs ?? remaining)); - React3.useEffect(() => setImageFailed(false), [qrSource]); - return h2( + React4.useEffect(() => setImageFailed(false), [qrSource]); + return h3( "div", { className: "bxf-card bxf-provisionCard" }, - h2( + h3( "div", { className: "bxf-cardBody bxf-qrLayout" }, - h2( + h3( "div", { className: "bxf-qrColumn" }, - h2( + h3( "div", { className: "bxf-qrFrame" }, - qrSource && !imageFailed ? h2("img", { + qrSource && !imageFailed ? h3("img", { src: qrSource, alt: "\u7528\u4E8E\u65B0\u589E DeepSeek Harness \u98DE\u4E66\u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) - }) : h2( + }) : h3( "div", { className: "bxf-qrFallback" }, - h2("div", null, h2(QrIcon), h2("span", null, "\u4E8C\u7EF4\u7801\u672A\u5C31\u7EEA\uFF0C\u8BF7\u6253\u5F00\u6388\u6743\u94FE\u63A5")) + h3("div", null, h3(QrIcon), h3("span", null, "\u4E8C\u7EF4\u7801\u672A\u5C31\u7EEA\uFF0C\u8BF7\u6253\u5F00\u6388\u6743\u94FE\u63A5")) ), - expired ? h2( + expired ? h3( "div", { className: "bxf-expiredOverlay", role: "status" }, - h2("div", null, "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548", h2("br"), "\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u626B\u7801") + h3("div", null, "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548", h3("br"), "\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u626B\u7801") ) : null ), - h2( + h3( "div", { className: "bxf-countdown", "aria-label": expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : `\u4E8C\u7EF4\u7801\u5269\u4F59 ${formatRemaining2(remaining)}` }, - h2( + h3( "div", { className: "bxf-countdownTop", "aria-hidden": "true" }, - h2("span", null, expired ? "\u7B49\u5F85\u5237\u65B0" : "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), - h2("strong", null, formatRemaining2(remaining)) + h3("span", null, expired ? "\u7B49\u5F85\u5237\u65B0" : "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), + h3("strong", null, formatRemaining2(remaining)) ), - h2( + h3( "div", { className: "bxf-progress", "aria-hidden": "true" }, - h2("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } }) + h3("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } }) ) ) ), - h2( + h3( "div", { className: "bxf-qrCopy" }, - h2( + h3( "div", { className: "bxf-stateLabel" }, - h2("span", { className: "bxf-dot", "data-tone": "warning" }), - h2("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA") + h3("span", { className: "bxf-dot", "data-tone": "warning" }), + h3("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA") ), - h2("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"), - h2("p", null, "\u626B\u7801\u53EA\u4F1A\u65B0\u589E\u4E00\u4E2A\u673A\u5668\u4EBA\uFF0C\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA\u4F1A\u7EE7\u7EED\u6B63\u5E38\u6536\u53D1\u6D88\u606F\u3002"), - h2( + h3("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"), + h3("p", null, "\u626B\u7801\u53EA\u4F1A\u65B0\u589E\u4E00\u4E2A\u673A\u5668\u4EBA\uFF0C\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA\u4F1A\u7EE7\u7EED\u6B63\u5E38\u6536\u53D1\u6D88\u606F\u3002"), + h3( "ol", { className: "bxf-steps" }, - h2("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"), - h2("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"), - h2("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA") + h3("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"), + h3("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"), + h3("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA") ), - h2( + h3( "div", { className: "bxf-actions" }, - expired ? h2(Button3, { + expired ? h3(Button3, { kind: "primary", - icon: h2(RefreshIcon), + icon: h3(RefreshIcon), onClick: onRefresh, disabled: busy - }, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u4E8C\u7EF4\u7801") : href ? h2("a", { + }, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u4E8C\u7EF4\u7801") : href ? h3("a", { className: "bxf-button bxf-link", "data-kind": "secondary", href, target: "_blank", rel: "noopener noreferrer" - }, h2(ExternalIcon), h2("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null, - !expired ? h2(Button3, { icon: h2(RefreshIcon), onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, - h2(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") + }, h3(ExternalIcon), h3("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null, + !expired ? h3(Button3, { icon: h3(RefreshIcon), onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, + h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") ) ) ) @@ -2255,58 +2276,58 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) { } function ProvisionProgress({ phase, onCancel, busy }) { const connecting = phase === "connecting"; - return h2( + return h3( "div", { className: "bxf-card bxf-provisionCard", "aria-busy": "true" }, - h2( + h3( "div", { className: "bxf-connecting bxf-connectingCompact" }, - h2( + h3( "div", { className: "bxf-connectingCopy" }, - h2( + h3( "div", { className: "bxf-orbit" }, - h2( + h3( "div", { className: "bxf-orbitCore" }, - connecting ? h2(RobotIcon, { size: 24 }) : h2(SparkIcon, { size: 24 }) + connecting ? h3(RobotIcon, { size: 24 }) : h3(SparkIcon, { size: 24 }) ) ), - h2("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"), - h2("p", null, connecting ? "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"), - connecting ? h2( + h3("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"), + h3("p", null, connecting ? "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"), + connecting ? h3( "div", { className: "bxf-actions", style: { justifyContent: "center" } }, - h2(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") + h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") ) : null ) ) ); } function ProvisionError2({ error, onRetry, onCancel, busy }) { - return h2( + return h3( "div", { className: "bxf-card bxf-provisionCard" }, - h2( + h3( "div", { className: "bxf-inlineError", role: "alert" }, - h2("div", { className: "bxf-errorIcon" }, h2(AlertIcon)), - h2( + h3("div", { className: "bxf-errorIcon" }, h3(AlertIcon)), + h3( "div", null, - h2("h3", null, "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"), - h2("p", null, error.message), - error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null, - h2( + h3("h3", null, "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"), + h3("p", null, error.message), + error.code ? h3("span", { className: "bxf-errorCode" }, error.code) : null, + h3( "div", { className: "bxf-actions" }, - h2( + h3( Button3, - { kind: "primary", icon: h2(RefreshIcon), onClick: onRetry, disabled: busy }, + { kind: "primary", icon: h3(RefreshIcon), onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801" ), - h2(Button3, { onClick: onCancel, disabled: busy }, "\u5173\u95ED") + h3(Button3, { onClick: onCancel, disabled: busy }, "\u5173\u95ED") ) ) ) @@ -2331,12 +2352,12 @@ function formatCheckedTime(timestamp3) { } } function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) { - const cancelRef = React3.useRef(null); + const cancelRef = React4.useRef(null); const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-"); const titleId = `bxf-remove-title-${idPart}`; const descriptionId = `bxf-remove-description-${idPart}`; - React3.useEffect(() => cancelRef.current?.focus(), []); - return h2( + React4.useEffect(() => cancelRef.current?.focus(), []); + return h3( "div", { className: "bxf-confirm", @@ -2350,17 +2371,17 @@ function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) { } } }, - h2("h4", { id: titleId }, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${bot.bot.name}\u201D\uFF1F`), - h2( + h3("h4", { id: titleId }, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${bot.bot.name}\u201D\uFF1F`), + h3( "p", { id: descriptionId }, "\u6B64\u64CD\u4F5C\u4F1A\u505C\u6B62\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u4FDD\u5B58\u5728\u672C\u673A\u7684\u63A5\u5165\u914D\u7F6E\u548C\u51ED\u636E\u3002\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u5E94\u7528\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E5F\u4E0D\u53D7\u5F71\u54CD\u3002" ), - h2( + h3( "div", { className: "bxf-actions" }, - h2(Button3, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), - h2( + h3(Button3, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), + h3( Button3, { kind: "danger", onClick: onConfirm, disabled: busy }, busy ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165" @@ -2385,8 +2406,7 @@ function BotCard({ const tone = stateForDisplay === "connected" ? "success" : stateForDisplay === "connecting" ? "warning" : "error"; const summary = actionError?.message ?? connection.error?.message ?? health.summary; const titleId = `bxf-bot-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`; - const domainLabel = bot.domain === "lark" ? "Lark" : "\u98DE\u4E66"; - return h2( + return h3( "article", { className: "bxf-card bxf-botCard", @@ -2395,74 +2415,72 @@ function BotCard({ tabIndex: -1, ref: cardRef }, - h2( + h3( "div", { className: "bxf-cardBody" }, - h2( + h3( "div", { className: "bxf-connectedTop" }, - h2( + h3( "div", { className: "bxf-botIdentity" }, - bot.avatarUrl ? h2("div", { className: "bxf-avatar" }, h2("img", { src: bot.avatarUrl, alt: "" })) : h2(BrandMark, { compact: true }), - h2( + h3( + "div", + { className: "bxf-avatar", "aria-hidden": "true" }, + h3(FeishuLogoGlyph, { size: 34 }) + ), + h3( "div", { className: "bxf-botName" }, - h2("h3", { id: titleId, title: bot.name }, bot.name), - h2("p", null, bot.tenantName ? `${bot.tenantName} \xB7 ${domainLabel}\u673A\u5668\u4EBA` : `${domainLabel}\u673A\u5668\u4EBA`) + h3("h3", { id: titleId, title: bot.name }, bot.name), + h3("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58") ) ), - h2( + h3( "div", { className: "bxf-healthPill", "data-health": stateForDisplay }, - h2("span", { className: "bxf-dot", "data-tone": tone }), - h2("span", null, HEALTH_LABELS[stateForDisplay] ?? "\u72B6\u6001\u672A\u77E5") + h3("span", { className: "bxf-dot", "data-tone": tone }), + h3("span", null, HEALTH_LABELS[stateForDisplay] ?? "\u72B6\u6001\u672A\u77E5") ) ), - h2( + h3( "dl", { className: "bxf-statusGrid" }, - h2( + h3( "div", { className: "bxf-metric" }, - h2("dt", null, "\u6D88\u606F\u901A\u9053"), - h2("dd", null, connected ? "\u957F\u8FDE\u63A5" : stateForDisplay === "connecting" ? "\u8FDE\u63A5\u4E2D" : "\u5DF2\u65AD\u5F00") + h3("dt", null, "\u6D88\u606F\u901A\u9053"), + h3("dd", null, connected ? "\u957F\u8FDE\u63A5" : stateForDisplay === "connecting" ? "\u8FDE\u63A5\u4E2D" : "\u5DF2\u65AD\u5F00") ), - h2( + h3( "div", { className: "bxf-metric" }, - h2("dt", null, "\u5E94\u7528\u6807\u8BC6"), - h2("dd", { title: bot.appIdMasked }, bot.appIdMasked ?? "\u5DF2\u5B89\u5168\u4FDD\u5B58") - ), - h2( - "div", - { className: "bxf-metric" }, - h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), - h2("dd", null, formatCheckedTime(health.lastCheckedAt)) + h3("dt", null, "\u6700\u8FD1\u68C0\u67E5"), + h3("dd", null, formatCheckedTime(health.lastCheckedAt)) ) ), - h2("div", { className: "bxf-divider" }), - h2( + h3("div", { className: "bxf-divider" }), + h3( "div", { className: "bxf-connectedFooter" }, - h2( + h3( "div", { className: "bxf-healthSummary", "data-error": actionError || connection.error ? "true" : void 0 }, - h2("strong", null, "\u8FDE\u63A5\u72B6\u6001\uFF1A"), - h2("span", null, summary) + h3("strong", null, "\u8FDE\u63A5\u72B6\u6001\uFF1A"), + h3("span", null, summary) ), - h2( + h3( "div", { className: "bxf-actions bxf-botActions" }, - h2(Button3, { + h3(Button3, { size: "small", - icon: h2(RefreshIcon), + icon: h3(RefreshIcon), onClick: onReconnect, disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : void 0, "aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}` }, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), - h2(Button3, { + h3(Button3, { size: "small", kind: "danger", onClick: onRequestRemove, @@ -2473,7 +2491,7 @@ function BotCard({ ) ) ), - removing ? h2(RemoveConfirmation2, { + removing ? h3(RemoveConfirmation2, { bot: connection, busy: busy === "delete", onConfirm: onConfirmRemove, @@ -2482,22 +2500,22 @@ function BotCard({ ); } function BotList(props) { - return h2( + return h3( "section", { className: "bxf-listSection", "aria-labelledby": "bxf-bot-list-title" }, - h2( + h3( "div", { className: "bxf-listHeading" }, - h2("h3", { id: "bxf-bot-list-title" }, "\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA"), - h2("span", null, `${props.bots.length} \u4E2A`) + h3("h3", { id: "bxf-bot-list-title" }, "\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA"), + h3("span", null, `${props.bots.length} \u4E2A`) ), - h2( + h3( "ul", { className: "bxf-botList", role: "list" }, - props.bots.map((bot) => h2( + props.bots.map((bot) => h3( "li", { key: bot.botId }, - h2(BotCard, { + h3(BotCard, { connection: bot, busy: props.busyByBot[bot.botId], actionError: props.errorsByBot[bot.botId], @@ -2514,25 +2532,25 @@ function BotList(props) { ); } function PageError({ error, onRetry, busy }) { - return h2( + return h3( "div", { className: "bxf-card" }, - h2( + h3( "div", { className: "bxf-error", role: "alert" }, - h2("div", { className: "bxf-errorIcon" }, h2(AlertIcon)), - h2( + h3("div", { className: "bxf-errorIcon" }, h3(AlertIcon)), + h3( "div", null, - h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA"), - h2("p", null, error.message), - error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null, - h2( + h3("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA"), + h3("p", null, error.message), + error.code ? h3("span", { className: "bxf-errorCode" }, error.code) : null, + h3( "div", { className: "bxf-actions" }, - h2( + h3( Button3, - { kind: "primary", icon: h2(RefreshIcon), onClick: onRetry, disabled: busy }, + { kind: "primary", icon: h3(RefreshIcon), onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u8BFB\u53D6" ) ) @@ -2564,7 +2582,7 @@ function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = fal }; } function FeishuSettingsTab({ rpcCall }) { - const [model, setModel] = React3.useState({ + const [model, setModel] = React4.useState({ phase: "loading", revision: 0, bots: [], @@ -2573,28 +2591,28 @@ function FeishuSettingsTab({ rpcCall }) { pageError: null, statusError: null }); - const [pageBusy, setPageBusy] = React3.useState(false); - const [provisionBusy, setProvisionBusy] = React3.useState(false); - const [busyByBot, setBusyByBot] = React3.useState({}); - const [errorsByBot, setErrorsByBot] = React3.useState({}); - const [removeTargetId, setRemoveTargetId] = React3.useState(null); - const [announcement, setAnnouncement] = React3.useState(""); - const [now, setNow] = React3.useState(() => Date.now()); - const [focusBotId, setFocusBotId] = React3.useState(null); - const cardRefs = React3.useRef(/* @__PURE__ */ new Map()); - const removeButtonRefs = React3.useRef(/* @__PURE__ */ new Map()); - const addButtonRef = React3.useRef(null); + const [pageBusy, setPageBusy] = React4.useState(false); + const [provisionBusy, setProvisionBusy] = React4.useState(false); + const [busyByBot, setBusyByBot] = React4.useState({}); + const [errorsByBot, setErrorsByBot] = React4.useState({}); + const [removeTargetId, setRemoveTargetId] = React4.useState(null); + const [announcement, setAnnouncement] = React4.useState(""); + const [now, setNow] = React4.useState(() => Date.now()); + const [focusBotId, setFocusBotId] = React4.useState(null); + const cardRefs = React4.useRef(/* @__PURE__ */ new Map()); + const removeButtonRefs = React4.useRef(/* @__PURE__ */ new Map()); + const addButtonRef = React4.useRef(null); const scheduleAnimationFrame = useAnimationFrameScheduler(); - const announce = React3.useCallback((message) => { + const announce = React4.useCallback((message) => { setAnnouncement(""); scheduleAnimationFrame(() => { if (message) setAnnouncement(message); }, "announcement"); }, [scheduleAnimationFrame]); - const invoke = React3.useCallback(async (endpoint, payload = {}, signal) => { + const invoke = React4.useCallback(async (endpoint, payload = {}, signal) => { return unwrapRpcResult2(await rpcCall(endpoint, payload, signal)); }, [rpcCall]); - const mergeSnapshot = React3.useCallback((snapshot, { restoreProvisioning = false } = {}) => { + const mergeSnapshot = React4.useCallback((snapshot, { restoreProvisioning = false } = {}) => { const now2 = Date.now(); setModel((current) => mergeFeishuSnapshotState( current, @@ -2602,7 +2620,7 @@ function FeishuSettingsTab({ rpcCall }) { { restoreProvisioning, now: now2 } )); }, []); - const loadStatus = React3.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => { + const loadStatus = React4.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => { if (!silent) setPageBusy(true); try { const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.status, {}, signal)); @@ -2618,12 +2636,12 @@ function FeishuSettingsTab({ rpcCall }) { if (!silent && !signal?.aborted) setPageBusy(false); } }, [invoke, mergeSnapshot]); - React3.useEffect(() => { + React4.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); - React3.useEffect(() => { + React4.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let inFlight = false; @@ -2642,7 +2660,7 @@ function FeishuSettingsTab({ rpcCall }) { window.clearInterval(timer); }; }, [loadStatus, model.phase]); - React3.useEffect(() => { + React4.useEffect(() => { if (!focusBotId) return; const node = cardRefs.current.get(focusBotId); if (!node) return; @@ -2650,7 +2668,7 @@ function FeishuSettingsTab({ rpcCall }) { node.focus({ preventScroll: true }); setFocusBotId(null); }, [focusBotId, model.bots]); - const startProvisioning = React3.useCallback(async ({ replace = false } = {}) => { + const startProvisioning = React4.useCallback(async ({ replace = false } = {}) => { setProvisionBusy(true); announce(""); const previousAttemptId = model.provisioning?.attemptId; @@ -2688,7 +2706,7 @@ function FeishuSettingsTab({ rpcCall }) { setProvisionBusy(false); } }, [announce, invoke, model.provisioning?.attemptId]); - const cancelProvisioning = React3.useCallback(async () => { + const cancelProvisioning = React4.useCallback(async () => { const attemptId = model.provisioning?.attemptId; setProvisionBusy(true); try { @@ -2710,7 +2728,7 @@ function FeishuSettingsTab({ rpcCall }) { const countdownPhase = model.provisioning?.phase; const countdownExpiresAt = model.provisioning?.expiresAt; const countdownExpired = model.provisioning?.expired; - React3.useEffect(() => { + React4.useEffect(() => { if (!countdownAttemptId || countdownPhase !== "qr" || countdownExpired) return void 0; const tick = () => { const timestamp3 = Date.now(); @@ -2723,7 +2741,7 @@ function FeishuSettingsTab({ rpcCall }) { const timer = window.setInterval(tick, 1e3); return () => window.clearInterval(timer); }, [countdownAttemptId, countdownPhase, countdownExpiresAt, countdownExpired]); - React3.useEffect(() => { + React4.useEffect(() => { const provision2 = model.provisioning; if (!provision2 || !["qr", "connecting"].includes(provision2.phase) || !provision2.attemptId || provision2.expired) return void 0; const controller = new AbortController(); @@ -2787,7 +2805,7 @@ function FeishuSettingsTab({ rpcCall }) { window.clearTimeout(timer); }; }, [announce, invoke, loadStatus, model.provisioning]); - const setBotBusy = React3.useCallback((botId, value) => { + const setBotBusy = React4.useCallback((botId, value) => { setBusyByBot((current) => { const next = { ...current }; if (value) next[botId] = value; @@ -2795,7 +2813,7 @@ function FeishuSettingsTab({ rpcCall }) { return next; }); }, []); - const setBotError = React3.useCallback((botId, error) => { + const setBotError = React4.useCallback((botId, error) => { setErrorsByBot((current) => { const next = { ...current }; if (error) next[botId] = presentError2(error); @@ -2803,7 +2821,7 @@ function FeishuSettingsTab({ rpcCall }) { return next; }); }, []); - const reconnectOneBot = React3.useCallback(async (connection) => { + const reconnectOneBot = React4.useCallback(async (connection) => { const { botId, bot } = connection; setBotBusy(botId, "reconnect"); setBotError(botId, null); @@ -2826,15 +2844,15 @@ function FeishuSettingsTab({ rpcCall }) { setBotBusy(botId, null); } }, [announce, invoke, mergeSnapshot, setBotBusy, setBotError]); - const requestRemove = React3.useCallback((connection) => { + const requestRemove = React4.useCallback((connection) => { setRemoveTargetId(connection.botId); }, []); - const cancelRemove = React3.useCallback(() => { + const cancelRemove = React4.useCallback(() => { const botId = removeTargetId; setRemoveTargetId(null); scheduleAnimationFrame(() => removeButtonRefs.current.get(botId)?.focus(), "focus"); }, [removeTargetId, scheduleAnimationFrame]); - const confirmRemove = React3.useCallback(async (connection) => { + const confirmRemove = React4.useCallback(async (connection) => { const { botId, bot } = connection; setBotBusy(botId, "delete"); setBotError(botId, null); @@ -2865,9 +2883,9 @@ function FeishuSettingsTab({ rpcCall }) { const provision = model.provisioning; let provisionContent = null; if (provision?.phase === "creating") { - provisionContent = h2(ProvisionProgress, { phase: "creating", busy: provisionBusy }); + provisionContent = h3(ProvisionProgress, { phase: "creating", busy: provisionBusy }); } else if (provision?.phase === "qr") { - provisionContent = h2(QrPane, { + provisionContent = h3(QrPane, { provision, now, onRefresh: () => void startProvisioning({ replace: true }), @@ -2875,60 +2893,60 @@ function FeishuSettingsTab({ rpcCall }) { busy: provisionBusy || model.phase !== "ready" }); } else if (provision?.phase === "connecting") { - provisionContent = h2(ProvisionProgress, { + provisionContent = h3(ProvisionProgress, { phase: "connecting", onCancel: () => void cancelProvisioning(), busy: provisionBusy }); } else if (provision?.phase === "error") { - provisionContent = h2(ProvisionError2, { + provisionContent = h3(ProvisionError2, { error: provision.error, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onCancel: () => void cancelProvisioning(), busy: provisionBusy }); } - const setCardRef = React3.useCallback((botId, node) => { + const setCardRef = React4.useCallback((botId, node) => { if (node) cardRefs.current.set(botId, node); else cardRefs.current.delete(botId); }, []); - const setRemoveButtonRef = React3.useCallback((botId, node) => { + const setRemoveButtonRef = React4.useCallback((botId, node) => { if (node) removeButtonRefs.current.set(botId, node); else removeButtonRefs.current.delete(botId); }, []); - return h2( + return h3( "section", { className: "bxf-page", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" }, - h2(Heading2, { + h3(Heading2, { totals: model.totals, onAdd: () => void startProvisioning(), adding: Boolean(provision), busy: provisionBusy, addButtonRef }), - h2("div", { + h3("div", { className: "bxf-visuallyHidden", role: "status", "aria-live": "polite", "aria-atomic": "true" }, announcement), - model.statusError ? h2( + model.statusError ? h3( "div", { className: "bxf-statusNotice", role: "status" }, - h2(AlertIcon, { size: 16 }), - h2("span", null, `\u72B6\u6001\u81EA\u52A8\u5237\u65B0\u5931\u8D25\uFF1A${model.statusError.message}`), - h2(Button3, { size: "small", onClick: () => void loadStatus({ silent: true }), disabled: pageBusy }, "\u7ACB\u5373\u91CD\u8BD5") + h3(AlertIcon, { size: 16 }), + h3("span", null, `\u72B6\u6001\u81EA\u52A8\u5237\u65B0\u5931\u8D25\uFF1A${model.statusError.message}`), + h3(Button3, { size: "small", onClick: () => void loadStatus({ silent: true }), disabled: pageBusy }, "\u7ACB\u5373\u91CD\u8BD5") ) : null, - model.phase === "loading" ? h2(LoadingView2) : model.phase === "error" ? h2(PageError, { + model.phase === "loading" ? h3(LoadingView2) : model.phase === "error" ? h3(PageError, { error: model.pageError ?? { message: "\u65E0\u6CD5\u8BFB\u53D6\u8FDE\u63A5\u72B6\u6001" }, onRetry: () => void loadStatus(), busy: pageBusy - }) : h2( - React3.Fragment, + }) : h3( + React4.Fragment, null, provisionContent, - model.bots.length === 0 && !provision ? h2(EmptyView2, { onStart: () => void startProvisioning(), busy: provisionBusy }) : null, - model.bots.length > 0 ? h2(BotList, { + model.bots.length === 0 && !provision ? h3(EmptyView2, { onStart: () => void startProvisioning(), busy: provisionBusy }) : null, + model.bots.length > 0 ? h3(BotList, { bots: model.bots, busyByBot, errorsByBot, @@ -2945,7 +2963,7 @@ function FeishuSettingsTab({ rpcCall }) { } // plugin-src/client/channels/weixin/index.js -var React4 = __toESM(require("react"), 1); +var React5 = __toESM(require("react"), 1); // plugin-src/client/channels/weixin/api.js var WEIXIN_RPC_CHANNEL = "/weixin"; @@ -3165,7 +3183,7 @@ var CSS3 = String.raw` .dxw-accountIdentity h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; } .dxw-accountIdentity p { color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; margin-top: 4px; } .dxw-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; } -.dxw-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 20px 0; } +.dxw-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 20px 0; } .dxw-metric { padding: 12px 14px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } @@ -3208,37 +3226,9 @@ function installWeixinStyles() { } // plugin-src/client/channels/weixin/index.js -var h3 = React4.createElement; -function WeixinIcon({ size = 26 }) { - return h3( - "svg", - { - width: size, - height: size, - viewBox: "0 0 24 24", - fill: "none", - xmlns: "http://www.w3.org/2000/svg", - "aria-hidden": "true" - }, - h3("path", { - d: "M9.7 4.2c-4.15 0-7.5 2.72-7.5 6.08 0 1.91 1.08 3.61 2.78 4.72l-.7 2.35 2.75-1.37c.84.25 1.73.38 2.67.38 4.14 0 7.5-2.72 7.5-6.08S13.84 4.2 9.7 4.2Z", - fill: "currentColor", - opacity: ".96" - }), - h3("path", { - d: "M14.4 8.2c4.08 0 7.4 2.66 7.4 5.95 0 1.78-.97 3.38-2.5 4.47l.58 2-2.36-1.18c-.97.43-2.03.65-3.12.65-4.09 0-7.4-2.66-7.4-5.94 0-3.29 3.31-5.95 7.4-5.95Z", - fill: "currentColor", - stroke: "white", - strokeWidth: ".8" - }), - h3("circle", { cx: "7.25", cy: "9.5", r: "1", fill: "white" }), - h3("circle", { cx: "11.55", cy: "9.5", r: "1", fill: "white" }), - h3("circle", { cx: "12.1", cy: "13.2", r: ".9", fill: "white" }), - h3("circle", { cx: "16.3", cy: "13.2", r: ".9", fill: "white" }) - ); -} -var Button5 = React4.forwardRef(function Button6({ children, kind = "secondary", className = "", ...props }, ref) { - return h3("button", { +var h4 = React5.createElement; +var Button5 = React5.forwardRef(function Button6({ children, kind = "secondary", className = "", ...props }, ref) { + return h4("button", { ...props, ref, type: "button", @@ -3247,24 +3237,24 @@ var Button5 = React4.forwardRef(function Button6({ children, kind = "secondary", }, children); }); function Heading3({ totals, adding, busy, onAdd, addButtonRef }) { - return h3( + return h4( "div", { className: "dxw-heading" }, - h3( + h4( "div", { className: "dxw-tools" }, - totals.configured > 0 ? h3( + totals.configured > 0 ? h4( "div", { className: "dxw-badge" }, - h3("span", { className: "dxw-dot", "data-tone": totals.connected > 0 ? "success" : "warning" }), - h3("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) + h4("span", { className: "dxw-dot", "data-tone": totals.connected > 0 ? "success" : "warning" }), + h4("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) ) : null, - h3( + h4( "div", { className: "dxw-badge", title: "bot_token \u4EC5\u4FDD\u5B58\u5728 Harness Host \u51ED\u636E\u670D\u52A1\u4E2D" }, - h3("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A") + h4("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A") ), - h3(Button5, { + h4(Button5, { kind: "primary", onClick: onAdd, disabled: adding || busy, @@ -3274,127 +3264,127 @@ function Heading3({ totals, adding, busy, onAdd, addButtonRef }) { ); } function LoadingView3() { - return h3( + return h4( "div", { className: "dxw-card dxw-loading", "aria-busy": "true" }, - h3("div", { className: "dxw-spinner" }), - h3("span", null, "\u6B63\u5728\u8BFB\u53D6\u5FAE\u4FE1\u8FDE\u63A5\u72B6\u6001\u2026") + h4("div", { className: "dxw-spinner" }), + h4("span", null, "\u6B63\u5728\u8BFB\u53D6\u5FAE\u4FE1\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView3({ onStart, busy }) { - return h3( + return h4( "div", { className: "dxw-card" }, - h3( + h4( "div", { className: "dxw-cardBody dxw-empty" }, - h3( + h4( "div", null, - h3( + h4( "div", { className: "dxw-stateLabel" }, - h3("span", { className: "dxw-dot" }), - h3("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1") + h4("span", { className: "dxw-dot" }), + h4("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1") ), - h3("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u5C31\u80FD\u5728\u5FAE\u4FE1\u91CC\u4F7F\u7528 Harness"), - h3("p", null, "\u4E8C\u7EF4\u7801\u7531\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u7B7E\u53D1\u3002\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u5E76\u786E\u8BA4\u540E\uFF0C\u8D26\u53F7\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 bot_token\u3002"), - h3( + h4("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u5C31\u80FD\u5728\u5FAE\u4FE1\u91CC\u4F7F\u7528 Harness"), + h4("p", null, "\u4E8C\u7EF4\u7801\u7531\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u7B7E\u53D1\u3002\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u5E76\u786E\u8BA4\u540E\uFF0C\u8D26\u53F7\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 bot_token\u3002"), + h4( "div", { className: "dxw-actions" }, - h3( + h4( Button5, { kind: "primary", onClick: onStart, disabled: busy }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u5FAE\u4FE1\u4E8C\u7EF4\u7801" ) ) ), - h3("div", { className: "dxw-logo", "aria-hidden": "true" }, h3(WeixinIcon, { size: 64 })) + h4("div", { className: "dxw-logo", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 64 })) ) ); } function QrPanel2({ provision, now, busy, onRefresh, onCancel }) { - const [imageFailed, setImageFailed] = React4.useState(false); + const [imageFailed, setImageFailed] = React5.useState(false); const source = safeQrSource3(provision.qrCodeDataUrl); const href = safeVerificationUrl(provision.verificationUrl); const remaining = Math.max(0, provision.expiresAt - now); const expired = remaining === 0 || provision.status === "expired"; const duration = Math.max(1, provision.durationMs ?? 5 * 6e4); const progress = Math.round(Math.min(1, remaining / duration) * 100); - React4.useEffect(() => setImageFailed(false), [source]); - return h3( + React5.useEffect(() => setImageFailed(false), [source]); + return h4( "div", { className: "dxw-card" }, - h3( + h4( "div", { className: "dxw-cardBody dxw-qrLayout" }, - h3( + h4( "div", { className: "dxw-qrColumn" }, - h3( + h4( "div", { className: "dxw-qrFrame" }, - source && !imageFailed ? h3("img", { + source && !imageFailed ? h4("img", { src: source, alt: "\u7528\u4E8E\u628A\u5FAE\u4FE1\u673A\u5668\u4EBA\u7ED1\u5B9A\u5230 DeepSeek Harness \u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) - }) : h3("div", { className: "dxw-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"), - expired ? h3("div", { className: "dxw-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null + }) : h4("div", { className: "dxw-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"), + expired ? h4("div", { className: "dxw-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), - h3( + h4( "div", { className: "dxw-countdown" }, - h3("div", null, h3("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h3("strong", null, formatRemaining3(remaining))), - h3( + h4("div", null, h4("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h4("strong", null, formatRemaining3(remaining))), + h4( "div", { className: "dxw-progress", "aria-hidden": "true" }, - h3("span", { style: { "--dxw-progress": `${progress}%` } }) + h4("span", { style: { "--dxw-progress": `${progress}%` } }) ) ) ), - h3( + h4( "div", { className: "dxw-qrCopy" }, - h3( + h4( "div", { className: "dxw-stateLabel" }, - h3("span", { className: "dxw-dot", "data-tone": provision.status === "scanned" ? "success" : "warning" }), - h3("span", null, provision.status === "scanned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4" : "\u7B49\u5F85\u5FAE\u4FE1\u626B\u7801") + h4("span", { className: "dxw-dot", "data-tone": provision.status === "scanned" ? "success" : "warning" }), + h4("span", null, provision.status === "scanned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4" : "\u7B49\u5F85\u5FAE\u4FE1\u626B\u7801") ), - h3("h3", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u4F7F\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801"), - h3("p", null, "\u8BF7\u5728\u624B\u673A\u4E0A\u6838\u5BF9\u5E76\u786E\u8BA4\u6388\u6743\u3002\u90E8\u5206\u8D26\u53F7\u4F1A\u989D\u5916\u663E\u793A\u4E00\u4E2A\u914D\u5BF9\u6570\u5B57\uFF0C\u9875\u9762\u4F1A\u5728\u9700\u8981\u65F6\u63D0\u793A\u8F93\u5165\u3002"), - h3( + h4("h3", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u4F7F\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801"), + h4("p", null, "\u8BF7\u5728\u624B\u673A\u4E0A\u6838\u5BF9\u5E76\u786E\u8BA4\u6388\u6743\u3002\u90E8\u5206\u8D26\u53F7\u4F1A\u989D\u5916\u663E\u793A\u4E00\u4E2A\u914D\u5BF9\u6570\u5B57\uFF0C\u9875\u9762\u4F1A\u5728\u9700\u8981\u65F6\u63D0\u793A\u8F93\u5165\u3002"), + h4( "ol", { className: "dxw-steps" }, - h3("li", null, "\u6253\u5F00\u624B\u673A\u5FAE\u4FE1\u5E76\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), - h3("li", null, "\u5728\u5FAE\u4FE1\u4E2D\u786E\u8BA4\u8FDE\u63A5\u8BE5\u673A\u5668\u4EBA"), - h3("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6D88\u606F\u957F\u8F6E\u8BE2\u53D8\u4E3A\u5728\u7EBF") + h4("li", null, "\u6253\u5F00\u624B\u673A\u5FAE\u4FE1\u5E76\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), + h4("li", null, "\u5728\u5FAE\u4FE1\u4E2D\u786E\u8BA4\u8FDE\u63A5\u8BE5\u673A\u5668\u4EBA"), + h4("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6D88\u606F\u957F\u8F6E\u8BE2\u53D8\u4E3A\u5728\u7EBF") ), - h3( + h4( "div", { className: "dxw-actions" }, - expired ? h3(Button5, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, - href ? h3("a", { + expired ? h4(Button5, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, + href ? h4("a", { className: "dxw-button", href, target: "_blank", rel: "noopener noreferrer" }, "\u6253\u5F00\u5907\u7528\u94FE\u63A5") : null, - !expired ? h3(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, - h3(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") + !expired ? h4(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, + h4(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") ) ) ) ); } function VerificationPanel({ provision, busy, onSubmit, onCancel }) { - const [code, setCode] = React4.useState(""); + const [code, setCode] = React5.useState(""); const valid = /^\d{4,8}$/.test(code); - React4.useEffect(() => setCode(""), [provision.attemptId]); - return h3( + React5.useEffect(() => setCode(""), [provision.attemptId]); + return h4( "div", { className: "dxw-card" }, - h3( + h4( "form", { className: "dxw-verify", @@ -3403,18 +3393,18 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { if (valid && !busy) onSubmit(code); } }, - h3( + h4( "div", { className: "dxw-stateLabel" }, - h3("span", { className: "dxw-dot", "data-tone": "warning" }), - h3("span", null, "\u9700\u8981\u914D\u5BF9\u7801") + h4("span", { className: "dxw-dot", "data-tone": "warning" }), + h4("span", null, "\u9700\u8981\u914D\u5BF9\u7801") ), - h3("h3", null, "\u8F93\u5165\u624B\u673A\u5FAE\u4FE1\u663E\u793A\u7684\u6570\u5B57"), - h3("p", null, "\u8FD9\u662F\u5FAE\u4FE1\u9644\u52A0\u7684\u5B89\u5168\u786E\u8BA4\u6B65\u9AA4\u3002\u914D\u5BF9\u7801\u53EA\u7528\u4E8E\u672C\u6B21\u626B\u7801\u8F6E\u8BE2\uFF0C\u4E0D\u4F1A\u5199\u5165\u914D\u7F6E\u6216\u65E5\u5FD7\u3002"), - h3( + h4("h3", null, "\u8F93\u5165\u624B\u673A\u5FAE\u4FE1\u663E\u793A\u7684\u6570\u5B57"), + h4("p", null, "\u8FD9\u662F\u5FAE\u4FE1\u9644\u52A0\u7684\u5B89\u5168\u786E\u8BA4\u6B65\u9AA4\u3002\u914D\u5BF9\u7801\u53EA\u7528\u4E8E\u672C\u6B21\u626B\u7801\u8F6E\u8BE2\uFF0C\u4E0D\u4F1A\u5199\u5165\u914D\u7F6E\u6216\u65E5\u5FD7\u3002"), + h4( "div", { className: "dxw-codeRow" }, - h3("input", { + h4("input", { className: "dxw-input", value: code, inputMode: "numeric", @@ -3424,47 +3414,47 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { onChange: (event) => setCode(event.target.value.replace(/\D/g, "").slice(0, 8)), autoFocus: true }), - h3("button", { + h4("button", { type: "submit", className: "dxw-button", "data-kind": "primary", disabled: !valid || busy }, busy ? "\u6B63\u5728\u9A8C\u8BC1\u2026" : "\u7EE7\u7EED\u8FDE\u63A5") ), - h3(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u7ED1\u5B9A") + h4(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u7ED1\u5B9A") ) ); } function ProgressPanel2({ scanned, onCancel, busy }) { - return h3( + return h4( "div", { className: "dxw-card dxw-loading", "aria-busy": "true" }, - h3("div", { className: "dxw-spinner" }), - h3("h3", null, scanned ? "\u5FAE\u4FE1\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u52A8\u6D88\u606F\u8FDE\u63A5" : "\u6B63\u5728\u51C6\u5907\u5FAE\u4FE1\u4E8C\u7EF4\u7801"), - h3("p", null, scanned ? "\u6B63\u5728\u4FDD\u5B58\u51ED\u636E\u5E76\u9A8C\u8BC1 Harness \u4E0E\u5FAE\u4FE1\u957F\u8F6E\u8BE2\u3002" : "\u6B63\u5728\u8054\u7CFB\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u3002"), - onCancel ? h3( + h4("div", { className: "dxw-spinner" }), + h4("h3", null, scanned ? "\u5FAE\u4FE1\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u52A8\u6D88\u606F\u8FDE\u63A5" : "\u6B63\u5728\u51C6\u5907\u5FAE\u4FE1\u4E8C\u7EF4\u7801"), + h4("p", null, scanned ? "\u6B63\u5728\u4FDD\u5B58\u51ED\u636E\u5E76\u9A8C\u8BC1 Harness \u4E0E\u5FAE\u4FE1\u957F\u8F6E\u8BE2\u3002" : "\u6B63\u5728\u8054\u7CFB\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u3002"), + onCancel ? h4( "div", { className: "dxw-actions", style: { justifyContent: "center", marginTop: 14 } }, - h3(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") + h4(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") ) : null ); } function ProvisionError3({ provision, busy, onRetry, onClose }) { const error = provision.error ?? { code: "WEIXIN_PROVISION_FAILED", message: "\u5FAE\u4FE1\u7ED1\u5B9A\u6CA1\u6709\u5B8C\u6210" }; - return h3( + return h4( "div", { className: "dxw-card" }, - h3( + h4( "div", { className: "dxw-error", role: "alert" }, - h3("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u5FAE\u4FE1\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"), - h3("p", null, error.message), - h3("span", { className: "dxw-errorCode" }, error.code), - h3( + h4("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u5FAE\u4FE1\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"), + h4("p", null, error.message), + h4("span", { className: "dxw-errorCode" }, error.code), + h4( "div", { className: "dxw-actions" }, - h3(Button5, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), - h3(Button5, { onClick: onClose, disabled: busy }, "\u5173\u95ED") + h4(Button5, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), + h4(Button5, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) ) ); @@ -3484,76 +3474,70 @@ function checkedTime2(timestamp3) { function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) { const state = busy === "reconnect" ? "connecting" : account.state; const tone = account.connected ? "success" : state === "error" ? "error" : "warning"; - return h3( + return h4( "article", { className: "dxw-card", tabIndex: -1, "data-bot-id": account.botId }, - h3( + h4( "div", { className: "dxw-cardBody" }, - h3( + h4( "div", { className: "dxw-accountTop" }, - h3( + h4( "div", { className: "dxw-accountIdentity" }, - h3("div", { className: "dxw-avatar", "aria-hidden": "true" }, h3(WeixinIcon, { size: 27 })), - h3("div", null, h3("h3", null, account.bot.name), h3("p", null, account.bot.accountIdMasked)) + h4("div", { className: "dxw-avatar", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 27 })), + h4("div", null, h4("h3", null, account.bot.name), h4("p", null, account.bot.accountIdMasked)) ), - h3( + h4( "div", { className: "dxw-health" }, - h3("span", { className: "dxw-dot", "data-tone": tone }), - h3("span", null, account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA") + h4("span", { className: "dxw-dot", "data-tone": tone }), + h4("span", null, account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA") ) ), - h3( + h4( "dl", { className: "dxw-metrics" }, - h3( + h4( "div", { className: "dxw-metric" }, - h3("dt", null, "\u6D88\u606F\u901A\u9053"), - h3("dd", null, account.connected ? "iLink \u957F\u8F6E\u8BE2" : "\u79BB\u7EBF") + h4("dt", null, "\u6D88\u606F\u901A\u9053"), + h4("dd", null, account.connected ? "iLink \u957F\u8F6E\u8BE2" : "\u79BB\u7EBF") ), - h3( + h4( "div", { className: "dxw-metric" }, - h3("dt", null, "\u6536\u5230 / \u56DE\u590D"), - h3("dd", null, `${account.stats.messagesReceived} / ${account.stats.messagesReplied}`) - ), - h3( - "div", - { className: "dxw-metric" }, - h3("dt", null, "\u6700\u8FD1\u68C0\u67E5"), - h3("dd", null, checkedTime2(account.health.lastCheckedAt)) + h4("dt", null, "\u6700\u8FD1\u68C0\u67E5"), + h4("dd", null, checkedTime2(account.health.lastCheckedAt)) ) ), - h3( + h4( "div", { className: "dxw-accountFooter" }, - h3("div", { className: "dxw-summary" }, account.error?.message ?? account.health.summary), - h3( + h4("div", { className: "dxw-summary" }, account.error?.message ?? account.health.summary), + h4( "div", { className: "dxw-actions" }, - h3( + h4( Button5, { onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), - h3(Button5, { kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") + h4(Button5, { kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") ) ) ), - removing ? h3( + removing ? h4( "div", { className: "dxw-confirm", role: "alertdialog" }, - h3("strong", null, "\u4ECE\u6B64 Harness \u79FB\u9664\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\uFF1F"), - h3("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 bot_token\u3001\u8D26\u53F7\u914D\u7F6E\u548C\u4F1A\u8BDD\u6620\u5C04\u3002\u5176\u4ED6\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u53D7\u5F71\u54CD\u3002"), - h3( + h4("strong", null, "\u4ECE\u6B64 Harness \u79FB\u9664\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\uFF1F"), + h4("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 bot_token\u3001\u8D26\u53F7\u914D\u7F6E\u548C\u4F1A\u8BDD\u6620\u5C04\u3002\u5176\u4ED6\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u53D7\u5F71\u54CD\u3002"), + h4( "div", { className: "dxw-actions" }, - h3(Button5, { onClick: onCancelRemove, disabled: busy === "delete" }, "\u4FDD\u7559\u8D26\u53F7"), - h3( + h4(Button5, { onClick: onCancelRemove, disabled: busy === "delete" }, "\u4FDD\u7559\u8D26\u53F7"), + h4( Button5, { kind: "danger", onClick: onConfirmRemove, disabled: busy === "delete" }, busy === "delete" ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664" @@ -3563,14 +3547,14 @@ function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, o ); } function AccountList2(props) { - return h3( + return h4( "section", null, - h3("div", { className: "dxw-listHeading" }, h3("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7"), h3("span", null, `${props.bots.length} \u4E2A`)), - h3("ul", { className: "dxw-list" }, props.bots.map((account) => h3( + h4("div", { className: "dxw-listHeading" }, h4("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7"), h4("span", null, `${props.bots.length} \u4E2A`)), + h4("ul", { className: "dxw-list" }, props.bots.map((account) => h4( "li", { key: account.botId }, - h3(AccountCard2, { + h4(AccountCard2, { account, busy: props.busyByBot[account.botId], removing: props.removeTarget === account.botId, @@ -3593,31 +3577,31 @@ function mergeWeixinProvisioningSnapshot(current, incoming, { restoreProvisionin }; } function WeixinSettingsTab({ rpcCall }) { - const [model, setModel] = React4.useState({ + const [model, setModel] = React5.useState({ phase: "loading", bots: [], totals: EMPTY_TOTALS3, revision: 0, error: null }); - const [provision, setProvision] = React4.useState(null); - const [busy, setBusy] = React4.useState(false); - const [busyByBot, setBusyByBot] = React4.useState({}); - const [removeTarget, setRemoveTarget] = React4.useState(null); - const [notice, setNotice] = React4.useState(""); - const [now, setNow] = React4.useState(() => Date.now()); - const addButtonRef = React4.useRef(null); + const [provision, setProvision] = React5.useState(null); + const [busy, setBusy] = React5.useState(false); + const [busyByBot, setBusyByBot] = React5.useState({}); + const [removeTarget, setRemoveTarget] = React5.useState(null); + const [notice, setNotice] = React5.useState(""); + const [now, setNow] = React5.useState(() => Date.now()); + const addButtonRef = React5.useRef(null); const scheduleAnimationFrame = useAnimationFrameScheduler(); - const announce = React4.useCallback((value) => { + const announce = React5.useCallback((value) => { setNotice(""); scheduleAnimationFrame(() => { if (value) setNotice(value); }, "announcement"); }, [scheduleAnimationFrame]); - const invoke = React4.useCallback(async (endpoint, payload = {}, signal) => { + const invoke = React5.useCallback(async (endpoint, payload = {}, signal) => { return unwrapRpcResult3(await rpcCall(endpoint, payload, signal)); }, [rpcCall]); - const loadStatus = React4.useCallback(async ({ + const loadStatus = React5.useCallback(async ({ signal, silent = false, restoreProvisioning = false @@ -3651,12 +3635,12 @@ function WeixinSettingsTab({ rpcCall }) { return void 0; } }, [invoke]); - React4.useEffect(() => { + React5.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); - React4.useEffect(() => { + React5.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let running = false; @@ -3675,12 +3659,12 @@ function WeixinSettingsTab({ rpcCall }) { window.clearInterval(timer); }; }, [loadStatus, model.phase]); - React4.useEffect(() => { + React5.useEffect(() => { if (!provision || !["pending", "scanned"].includes(provision.status)) return void 0; const timer = window.setInterval(() => setNow(Date.now()), 1e3); return () => window.clearInterval(timer); }, [provision?.attemptId, provision?.status]); - const startProvisioning = React4.useCallback(async ({ replace = false } = {}) => { + const startProvisioning = React5.useCallback(async ({ replace = false } = {}) => { setBusy(true); try { if (replace && provision?.attemptId) { @@ -3701,7 +3685,7 @@ function WeixinSettingsTab({ rpcCall }) { setBusy(false); } }, [announce, invoke, provision?.attemptId]); - const cancelProvisioning = React4.useCallback(async () => { + const cancelProvisioning = React5.useCallback(async () => { setBusy(true); try { if (provision?.attemptId && !["failed", "expired", "cancelled"].includes(provision.status)) { @@ -3716,7 +3700,7 @@ function WeixinSettingsTab({ rpcCall }) { setBusy(false); } }, [announce, invoke, provision?.attemptId, provision?.status, scheduleAnimationFrame]); - const submitVerification = React4.useCallback(async (verifyCode) => { + const submitVerification = React5.useCallback(async (verifyCode) => { if (!provision?.attemptId) return; setBusy(true); try { @@ -3732,7 +3716,7 @@ function WeixinSettingsTab({ rpcCall }) { setBusy(false); } }, [announce, invoke, provision?.attemptId]); - React4.useEffect(() => { + React5.useEffect(() => { const attemptId = provision?.attemptId; if (!attemptId || !["pending", "scanned", "connecting"].includes(provision.status)) return void 0; const controller = new AbortController(); @@ -3780,7 +3764,7 @@ function WeixinSettingsTab({ rpcCall }) { controller.abort(); }; }, [announce, invoke, loadStatus, provision?.attemptId, provision?.status, provision?.pollIntervalMs]); - const setBotBusy = React4.useCallback((botId, value) => { + const setBotBusy = React5.useCallback((botId, value) => { setBusyByBot((current) => { const next = { ...current }; if (value) next[botId] = value; @@ -3788,7 +3772,7 @@ function WeixinSettingsTab({ rpcCall }) { return next; }); }, []); - const reconnect = React4.useCallback(async (account) => { + const reconnect = React5.useCallback(async (account) => { setBotBusy(account.botId, "reconnect"); try { const snapshot = normalizeSnapshot2(await invoke(WEIXIN_ENDPOINTS.reconnectBot, { botId: account.botId })); @@ -3801,7 +3785,7 @@ function WeixinSettingsTab({ rpcCall }) { setBotBusy(account.botId, null); } }, [announce, invoke, setBotBusy]); - const remove = React4.useCallback(async (account) => { + const remove = React5.useCallback(async (account) => { setBotBusy(account.botId, "delete"); try { const snapshot = normalizeSnapshot2(await invoke(WEIXIN_ENDPOINTS.deleteBot, { @@ -3819,9 +3803,9 @@ function WeixinSettingsTab({ rpcCall }) { }, [announce, invoke, setBotBusy]); let provisionView = null; if (provision?.status === "starting") { - provisionView = h3(ProgressPanel2, { busy }); + provisionView = h4(ProgressPanel2, { busy }); } else if (["pending", "scanned"].includes(provision?.status)) { - provisionView = h3(QrPanel2, { + provisionView = h4(QrPanel2, { provision, now, busy, @@ -3829,54 +3813,54 @@ function WeixinSettingsTab({ rpcCall }) { onCancel: () => void cancelProvisioning() }); } else if (provision?.status === "needs_verification") { - provisionView = h3(VerificationPanel, { + provisionView = h4(VerificationPanel, { provision, busy, onSubmit: (code) => void submitVerification(code), onCancel: () => void cancelProvisioning() }); } else if (provision?.status === "connecting") { - provisionView = h3(ProgressPanel2, { + provisionView = h4(ProgressPanel2, { scanned: true, busy, onCancel: () => void cancelProvisioning() }); } else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) { - provisionView = h3(ProvisionError3, { + provisionView = h4(ProvisionError3, { provision, busy, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onClose: () => void cancelProvisioning() }); } - return h3( + return h4( "section", { className: "dxw-page", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" }, - h3(Heading3, { + h4(Heading3, { totals: model.totals, adding: Boolean(provision), busy, onAdd: () => void startProvisioning(), addButtonRef }), - h3("div", { className: "dxw-visuallyHidden", role: "status", "aria-live": "polite" }, notice), - model.error && model.phase === "ready" ? h3("div", { className: "dxw-statusNotice" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, - model.phase === "loading" ? h3(LoadingView3) : model.phase === "error" ? h3( + h4("div", { className: "dxw-visuallyHidden", role: "status", "aria-live": "polite" }, notice), + model.error && model.phase === "ready" ? h4("div", { className: "dxw-statusNotice" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, + model.phase === "loading" ? h4(LoadingView3) : model.phase === "error" ? h4( "div", { className: "dxw-card" }, - h3( + h4( "div", { className: "dxw-error" }, - h3("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u5FAE\u4FE1\u72B6\u6001"), - h3("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), - h3(Button5, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") + h4("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u5FAE\u4FE1\u72B6\u6001"), + h4("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), + h4(Button5, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") ) - ) : h3( - React4.Fragment, + ) : h4( + React5.Fragment, null, provisionView, - model.bots.length === 0 && !provision ? h3(EmptyView3, { onStart: () => void startProvisioning(), busy }) : null, - model.bots.length > 0 ? h3(AccountList2, { + model.bots.length === 0 && !provision ? h4(EmptyView3, { onStart: () => void startProvisioning(), busy }) : null, + model.bots.length > 0 ? h4(AccountList2, { bots: model.bots, busyByBot, removeTarget, @@ -3902,9 +3886,8 @@ var CSS4 = String.raw` box-sizing: border-box; } .dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; } -.dim-title { display: flex; align-items: center; justify-content: space-between; gap: 22px; margin: 0 0 26px; } +.dim-title { margin: 0 0 26px; } .dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; } -.dim-channelCount { flex: none; padding: 6px 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 11px; white-space: nowrap; } .dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; } .dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 1px 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-line-border, #dfe1e5) transparent; } .dim-rail::-webkit-scrollbar { width: 4px; } @@ -3967,7 +3950,7 @@ function installImStyles() { } // plugin-src/client/index.js -var h4 = React5.createElement; +var h5 = React6.createElement; var name = "im-settings"; var inject = ["slots", "connection"]; var CHANNELS = Object.freeze([ @@ -3976,70 +3959,49 @@ var CHANNELS = Object.freeze([ { id: "dingtalk", label: "\u9489\u9489" } ]); function WeixinLogo() { - return h4( + return h5( "span", { className: "dim-logo dim-logoWeixin", "aria-hidden": "true" }, - h4( - "svg", - { viewBox: "0 0 24 24", focusable: "false" }, - h4("path", { - fill: "currentColor", - d: "M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z" - }) - ) + h5(WeixinLogoGlyph) ); } function FeishuLogo() { - return h4( + return h5( "span", { className: "dim-logo dim-logoFeishu", "aria-hidden": "true" }, - h4( - "svg", - { viewBox: "0 0 24 24", focusable: "false" }, - h4("path", { fill: "#00D6B9", d: "M7.2 4.5h7.6c1.2 0 2.1.55 2.7 1.58 1.05 1.8 1.55 3.45 1.58 4.95-2.04-.62-4.2-.15-6.22 1.45C11.3 9.7 9.42 7.04 7.2 4.5Z" }), - h4("path", { fill: "#1456B8", d: "M10.8 13.55c3.3-2.93 5.72-4.24 9.47-2.52-1.2 1.45-2.27 4.18-3.86 5.43-1.67 1.31-3.9.5-5.61-.64v-2.27Z" }), - h4("path", { fill: "#3370FF", d: "M4.4 8.35c3.47 3.61 7.25 6.1 10.33 5.7 1.06-.14 2.2-.72 3.4-1.72-1.04 2.65-2.6 4.8-5.06 6-2.46 1.2-5.56.52-7.42-.72A2.76 2.76 0 0 1 4.4 15.3V8.35Z" }) - ) + h5(FeishuLogoGlyph) ); } function DingtalkLogo() { - return h4( + return h5( "span", { className: "dim-logo dim-logoDingtalk", "aria-hidden": "true" }, - h4( - "svg", - { viewBox: "0 0 48 48", focusable: "false" }, - h4("path", { - fill: "currentColor", - 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" - }) - ) + h5(DingtalkLogoGlyph) ); } function ChannelLogo({ channel }) { - if (channel === "weixin") return h4(WeixinLogo); - if (channel === "feishu") return h4(FeishuLogo); - return h4(DingtalkLogo); + if (channel === "weixin") return h5(WeixinLogo); + if (channel === "feishu") return h5(FeishuLogo); + return h5(DingtalkLogo); } function IMSettingsTab({ dingtalkRpcCall, feishuRpcCall, weixinRpcCall }) { - const [selected, setSelected] = React5.useState("weixin"); + const [selected, setSelected] = React6.useState("weixin"); const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0]; - return h4( + return h5( "section", { className: "dim-page", "aria-label": "IM\u673A\u5668\u4EBA\u8BBE\u7F6E" }, - h4( + h5( "header", { className: "dim-title" }, - h4("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness"), - h4("div", { className: "dim-channelCount" }, `${CHANNELS.length} \u4E2A\u6E20\u9053`) + h5("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness") ), - h4( + h5( "div", { className: "dim-layout" }, - h4( + h5( "nav", { className: "dim-rail", role: "tablist", "aria-label": "IM \u6E20\u9053" }, - CHANNELS.map((channel) => h4( + CHANNELS.map((channel) => h5( "button", { key: channel.id, @@ -4051,21 +4013,21 @@ function IMSettingsTab({ dingtalkRpcCall, feishuRpcCall, weixinRpcCall }) { "aria-controls": `dim-panel-${channel.id}`, onClick: () => setSelected(channel.id) }, - h4(ChannelLogo, { channel: channel.id }), - h4( + h5(ChannelLogo, { channel: channel.id }), + h5( "span", { className: "dim-channelCopy" }, - h4("strong", null, channel.label) + h5("strong", null, channel.label) ) )) ), - h4("div", { className: "dim-divider", "aria-hidden": "true" }), - h4("main", { + h5("div", { className: "dim-divider", "aria-hidden": "true" }), + h5("main", { className: "dim-panel", role: "tabpanel", id: `dim-panel-${active.id}`, "aria-labelledby": `dim-tab-${active.id}` - }, active.id === "weixin" ? h4(WeixinSettingsTab, { rpcCall: weixinRpcCall }) : active.id === "feishu" ? h4(FeishuSettingsTab, { rpcCall: feishuRpcCall }) : h4(DingtalkSettingsTab, { rpcCall: dingtalkRpcCall })) + }, active.id === "weixin" ? h5(WeixinSettingsTab, { rpcCall: weixinRpcCall }) : active.id === "feishu" ? h5(FeishuSettingsTab, { rpcCall: feishuRpcCall }) : h5(DingtalkSettingsTab, { rpcCall: dingtalkRpcCall })) ) ); } diff --git a/plugin-src/client/channel-logos.js b/plugin-src/client/channel-logos.js new file mode 100644 index 0000000..02ad399 --- /dev/null +++ b/plugin-src/client/channel-logos.js @@ -0,0 +1,46 @@ +import * as React from 'react'; + +const h = React.createElement; + +function dimensions(size) { + return size === undefined ? {} : { width: size, height: size }; +} + +export function WeixinLogoGlyph({ size } = {}) { + return h('svg', { + ...dimensions(size), + viewBox: '0 0 24 24', + focusable: 'false', + 'aria-hidden': 'true', + 'data-im-channel-logo': 'weixin', + }, h('path', { + fill: 'currentColor', + d: 'M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z', + })); +} + +export function FeishuLogoGlyph({ size } = {}) { + return h('svg', { + ...dimensions(size), + viewBox: '0 0 24 24', + focusable: 'false', + 'aria-hidden': 'true', + 'data-im-channel-logo': 'feishu', + }, + h('path', { fill: '#00D6B9', d: 'M7.2 4.5h7.6c1.2 0 2.1.55 2.7 1.58 1.05 1.8 1.55 3.45 1.58 4.95-2.04-.62-4.2-.15-6.22 1.45C11.3 9.7 9.42 7.04 7.2 4.5Z' }), + h('path', { fill: '#1456B8', d: 'M10.8 13.55c3.3-2.93 5.72-4.24 9.47-2.52-1.2 1.45-2.27 4.18-3.86 5.43-1.67 1.31-3.9.5-5.61-.64v-2.27Z' }), + h('path', { fill: '#3370FF', d: 'M4.4 8.35c3.47 3.61 7.25 6.1 10.33 5.7 1.06-.14 2.2-.72 3.4-1.72-1.04 2.65-2.6 4.8-5.06 6-2.46 1.2-5.56.52-7.42-.72A2.76 2.76 0 0 1 4.4 15.3V8.35Z' })); +} + +export function DingtalkLogoGlyph({ size } = {}) { + return h('svg', { + ...dimensions(size), + viewBox: '0 0 48 48', + focusable: 'false', + 'aria-hidden': 'true', + 'data-im-channel-logo': 'dingtalk', + }, h('path', { + fill: 'currentColor', + 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', + })); +} diff --git a/plugin-src/client/channels/dingtalk/index.js b/plugin-src/client/channels/dingtalk/index.js index e94e052..9a711b5 100644 --- a/plugin-src/client/channels/dingtalk/index.js +++ b/plugin-src/client/channels/dingtalk/index.js @@ -131,8 +131,6 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { h('li', null, '使用已加入企业/组织的钉钉账号扫描左侧二维码'), h('li', null, '在授权页点击“一键创建新机器人”'), h('li', null, '保持本页打开,等待机器人自动连接')), - h('div', { className: 'ddt-brandNotice' }, - '钉钉官方授权页目前可能显示 OpenClaw 品牌,这是官方连接器授权页面,不影响机器人接入 DeepSeek Harness。'), h('div', { className: 'ddt-actions' }, expired ? h(Button, { kind: 'primary', onClick: onRefresh, disabled: busy }, '重新生成二维码') @@ -201,7 +199,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { busy ? '正在移除…' : '确认移除接入'))); } -function AccountCard({ +export function AccountCard({ account, busy, removing, @@ -226,8 +224,6 @@ function AccountCard({ h('dl', { className: 'ddt-metrics' }, h('div', { className: 'ddt-metric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'Stream 长连接' : '离线')), - h('div', { className: 'ddt-metric' }, h('dt', null, '收到 / 回复'), - h('dd', null, `${account.stats.messagesReceived} / ${account.stats.messagesReplied}`)), h('div', { className: 'ddt-metric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))), h('div', { className: 'ddt-accountFooter' }, diff --git a/plugin-src/client/channels/dingtalk/styles.js b/plugin-src/client/channels/dingtalk/styles.js index fa5dc57..8d53249 100644 --- a/plugin-src/client/channels/dingtalk/styles.js +++ b/plugin-src/client/channels/dingtalk/styles.js @@ -68,7 +68,6 @@ const CSS = String.raw` .ddt-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: ddt-step; } .ddt-steps li { position: relative; min-height: 28px; padding: 3px 0 3px 36px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 22px; counter-increment: ddt-step; } .ddt-steps li::before { content: counter(ddt-step); position: absolute; left: 0; top: 1px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px; color: var(--ddt-accent-deep); background: var(--ddt-accent-wash); font-size: 12px; font-weight: 700; } -.ddt-brandNotice { margin: 0 0 18px; padding: 10px 12px; border-left: 3px solid #91caff; border-radius: 0 8px 8px 0; color: var(--dsw-alias-label-secondary, #646a73); background: #f5f9ff; font-size: 12px; line-height: 1.55; } .ddt-loading { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } .ddt-loading h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; } .ddt-loading p { line-height: 1.6; } @@ -88,7 +87,7 @@ const CSS = String.raw` .ddt-accountIdentity h3 { overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; } .ddt-accountIdentity p { margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; } .ddt-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; } -.ddt-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } +.ddt-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } .ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } diff --git a/plugin-src/client/channels/feishu/index.js b/plugin-src/client/channels/feishu/index.js index f6b15b5..d48137a 100644 --- a/plugin-src/client/channels/feishu/index.js +++ b/plugin-src/client/channels/feishu/index.js @@ -1,5 +1,6 @@ import * as React from "react"; +import { FeishuLogoGlyph } from "../../channel-logos.js"; import { FEISHU_ENDPOINTS, FEISHU_RPC_CHANNEL, @@ -70,13 +71,6 @@ function SparkIcon({ size = 18 }) { ); } -function PlusIcon({ size = 17 }) { - return h(SvgIcon, { size }, h("path", { - d: "M12 5v14M5 12h14", stroke: "currentColor", strokeWidth: "1.8", - strokeLinecap: "round", - })); -} - function RefreshIcon({ size = 16 }) { return h(SvgIcon, { size }, h("path", { d: "M19 7.5V4m0 0h-3.5M19 4l-2.1 2.1A7 7 0 1 0 19 13", @@ -127,9 +121,8 @@ const Button = React.forwardRef(function Button( }, icon, h("span", null, children)); }); -function BrandMark({ compact = false }) { - return h("div", { className: compact ? "bxf-avatar" : "bxf-brandMark" }, - h(RobotIcon, { size: compact ? 25 : 34 })); +function BrandMark() { + return h("div", { className: "bxf-brandMark" }, h(RobotIcon, { size: 34 })); } function Heading({ totals, onAdd, adding, busy, addButtonRef }) { @@ -149,12 +142,12 @@ function Heading({ totals, onAdd, adding, busy, addButtonRef }) { h(Button, { kind: "primary", size: "small", - icon: h(PlusIcon), + className: "bxf-bindButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef, "aria-busy": busy ? "true" : undefined, - }, adding ? "正在添加" : "添加机器人"), + }, adding ? "正在绑定" : "扫码绑定机器人"), ), ); } @@ -363,7 +356,7 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) { ); } -function BotCard({ +export function BotCard({ connection, busy, actionError, @@ -388,8 +381,6 @@ function BotCard({ ?? connection.error?.message ?? health.summary; const titleId = `bxf-bot-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`; - const domainLabel = bot.domain === "lark" ? "Lark" : "飞书"; - return h("article", { className: "bxf-card bxf-botCard", "aria-labelledby": titleId, @@ -400,12 +391,11 @@ function BotCard({ h("div", { className: "bxf-cardBody" }, h("div", { className: "bxf-connectedTop" }, h("div", { className: "bxf-botIdentity" }, - bot.avatarUrl - ? h("div", { className: "bxf-avatar" }, h("img", { src: bot.avatarUrl, alt: "" })) - : h(BrandMark, { compact: true }), + h("div", { className: "bxf-avatar", "aria-hidden": "true" }, + h(FeishuLogoGlyph, { size: 34 })), h("div", { className: "bxf-botName" }, h("h3", { id: titleId, title: bot.name }, bot.name), - h("p", null, bot.tenantName ? `${bot.tenantName} · ${domainLabel}机器人` : `${domainLabel}机器人`)), + h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")), ), h("div", { className: "bxf-healthPill", "data-health": stateForDisplay }, h("span", { className: "bxf-dot", "data-tone": tone }), @@ -414,8 +404,6 @@ function BotCard({ h("dl", { className: "bxf-statusGrid" }, h("div", { className: "bxf-metric" }, h("dt", null, "消息通道"), h("dd", null, connected ? "长连接" : stateForDisplay === "connecting" ? "连接中" : "已断开")), - h("div", { className: "bxf-metric" }, h("dt", null, "应用标识"), - h("dd", { title: bot.appIdMasked }, bot.appIdMasked ?? "已安全保存")), h("div", { className: "bxf-metric" }, h("dt", null, "最近检查"), h("dd", null, formatCheckedTime(health.lastCheckedAt))), ), diff --git a/plugin-src/client/channels/feishu/styles.js b/plugin-src/client/channels/feishu/styles.js index d75dcd1..f319ce5 100644 --- a/plugin-src/client/channels/feishu/styles.js +++ b/plugin-src/client/channels/feishu/styles.js @@ -56,11 +56,12 @@ const CSS = String.raw` } .bxf-headingTools { + width: 100%; flex: none; display: flex; align-items: center; justify-content: flex-end; - flex-wrap: wrap; + flex-wrap: nowrap; gap: 8px; } @@ -246,6 +247,7 @@ const CSS = String.raw` .bxf-button[data-kind="danger"] { color: var(--bxf-error); } .bxf-button[data-size="small"] { min-height: 32px; padding: 5px 10px; font-size: 12px; } +.bxf-bindButton { flex: none; white-space: nowrap; } .bxf-provisionCard { border-color: color-mix(in srgb, var(--bxf-accent) 32%, var(--dsw-alias-border-l2, #dee0e3)); @@ -408,17 +410,16 @@ const CSS = String.raw` .bxf-connectedTop { display: flex; align-items: center; justify-content: space-between; gap: 20px; } .bxf-botIdentity { min-width: 0; display: flex; align-items: center; gap: 13px; } -.bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border-radius: 14px; color: var(--bxf-accent); background: color-mix(in srgb, var(--bxf-accent) 9%, var(--dsw-alias-bg-layer-1, #fff)); } -.bxf-avatar img { width: 100%; height: 100%; object-fit: cover; } +.bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 3px rgb(31 35 41 / 7%); } .bxf-botName { min-width: 0; } .bxf-botName h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 17px; line-height: 24px; font-weight: 650; } -.bxf-botName p { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: 18px; margin-top: 2px; } +.bxf-botName p { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-family: var(--ds-font-family-code, monospace); font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; } .bxf-healthPill { flex: none; display: inline-flex; align-items: center; gap: 7px; min-height: 28px; border-radius: 999px; padding: 4px 10px; color: var(--bxf-success); background: color-mix(in srgb, var(--bxf-success) 10%, transparent); font-size: 12px; font-weight: 600; line-height: 18px; } .bxf-healthPill[data-health="degraded"], .bxf-healthPill[data-health="checking"], .bxf-healthPill[data-health="connecting"] { color: var(--bxf-warning); background: color-mix(in srgb, var(--bxf-warning) 10%, transparent); } .bxf-healthPill[data-health="offline"], .bxf-healthPill[data-health="error"] { color: var(--bxf-error); background: color-mix(in srgb, var(--bxf-error) 10%, transparent); } -.bxf-statusGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 22px; } +.bxf-statusGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 22px; } .bxf-metric { min-width: 0; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 9px; padding: 12px 13px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); } .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; } .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; } @@ -476,15 +477,13 @@ const CSS = String.raw` @keyframes bxf-shimmer { to { background-position: -220% 0; } } @container (max-width: 620px) { - .bxf-heading { flex-direction: column; gap: 10px; } - .bxf-headingTools { width: 100%; justify-content: flex-start; } - .bxf-headingTools .bxf-button { margin-left: auto; } + .bxf-headingTools { gap: 6px; } + .bxf-headingTools .bxf-totalBadge, .bxf-headingTools .bxf-localBadge { padding-inline: 8px; } + .bxf-headingTools .bxf-localBadge { gap: 4px; } + .bxf-headingTools .bxf-bindButton { padding-inline: 10px; } } @media (max-width: 680px) { - .bxf-heading { flex-direction: column; gap: 10px; } - .bxf-headingTools { width: 100%; justify-content: flex-start; } - .bxf-headingTools .bxf-button { margin-left: auto; } .bxf-intro { grid-template-columns: minmax(0, 1fr); } .bxf-markStage { display: none; } .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } diff --git a/plugin-src/client/channels/weixin/index.js b/plugin-src/client/channels/weixin/index.js index f390378..d2de587 100644 --- a/plugin-src/client/channels/weixin/index.js +++ b/plugin-src/client/channels/weixin/index.js @@ -1,5 +1,6 @@ import * as React from 'react'; +import { WeixinLogoGlyph } from '../../channel-logos.js'; import { WEIXIN_ENDPOINTS, WEIXIN_RPC_CHANNEL, @@ -19,29 +20,6 @@ const h = React.createElement; export const name = 'weixin-settings'; export const inject = ['slots', 'connection']; -function WeixinIcon({ size = 26 }) { - return h('svg', { - width: size, - height: size, - viewBox: '0 0 24 24', - fill: 'none', - xmlns: 'http://www.w3.org/2000/svg', - 'aria-hidden': 'true', - }, - h('path', { - d: 'M9.7 4.2c-4.15 0-7.5 2.72-7.5 6.08 0 1.91 1.08 3.61 2.78 4.72l-.7 2.35 2.75-1.37c.84.25 1.73.38 2.67.38 4.14 0 7.5-2.72 7.5-6.08S13.84 4.2 9.7 4.2Z', - fill: 'currentColor', opacity: '.96', - }), - h('path', { - d: 'M14.4 8.2c4.08 0 7.4 2.66 7.4 5.95 0 1.78-.97 3.38-2.5 4.47l.58 2-2.36-1.18c-.97.43-2.03.65-3.12.65-4.09 0-7.4-2.66-7.4-5.94 0-3.29 3.31-5.95 7.4-5.95Z', - fill: 'currentColor', stroke: 'white', strokeWidth: '.8', - }), - h('circle', { cx: '7.25', cy: '9.5', r: '1', fill: 'white' }), - h('circle', { cx: '11.55', cy: '9.5', r: '1', fill: 'white' }), - h('circle', { cx: '12.1', cy: '13.2', r: '.9', fill: 'white' }), - h('circle', { cx: '16.3', cy: '13.2', r: '.9', fill: 'white' })); -} - const Button = React.forwardRef(function Button( { children, kind = 'secondary', className = '', ...props }, ref, @@ -93,7 +71,7 @@ function EmptyView({ onStart, busy }) { h(Button, { kind: 'primary', onClick: onStart, disabled: busy }, busy ? '正在生成二维码…' : '生成微信二维码')), ), - h('div', { className: 'dxw-logo', 'aria-hidden': 'true' }, h(WeixinIcon, { size: 64 })), + h('div', { className: 'dxw-logo', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 64 })), )); } @@ -216,14 +194,14 @@ function checkedTime(timestamp) { } } -function AccountCard({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) { +export function AccountCard({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) { const state = busy === 'reconnect' ? 'connecting' : account.state; const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning'; return h('article', { className: 'dxw-card', tabIndex: -1, 'data-bot-id': account.botId }, h('div', { className: 'dxw-cardBody' }, h('div', { className: 'dxw-accountTop' }, h('div', { className: 'dxw-accountIdentity' }, - h('div', { className: 'dxw-avatar', 'aria-hidden': 'true' }, h(WeixinIcon, { size: 27 })), + h('div', { className: 'dxw-avatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })), h('div', null, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))), h('div', { className: 'dxw-health' }, h('span', { className: 'dxw-dot', 'data-tone': tone }), @@ -231,8 +209,6 @@ function AccountCard({ account, busy, removing, onReconnect, onRequestRemove, on h('dl', { className: 'dxw-metrics' }, h('div', { className: 'dxw-metric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'iLink 长轮询' : '离线')), - h('div', { className: 'dxw-metric' }, h('dt', null, '收到 / 回复'), - h('dd', null, `${account.stats.messagesReceived} / ${account.stats.messagesReplied}`)), h('div', { className: 'dxw-metric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))), h('div', { className: 'dxw-accountFooter' }, diff --git a/plugin-src/client/channels/weixin/styles.js b/plugin-src/client/channels/weixin/styles.js index 06d4ace..05b1619 100644 --- a/plugin-src/client/channels/weixin/styles.js +++ b/plugin-src/client/channels/weixin/styles.js @@ -78,7 +78,7 @@ const CSS = String.raw` .dxw-accountIdentity h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; } .dxw-accountIdentity p { color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; margin-top: 4px; } .dxw-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; } -.dxw-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 20px 0; } +.dxw-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 20px 0; } .dxw-metric { padding: 12px 14px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } diff --git a/plugin-src/client/index.js b/plugin-src/client/index.js index 60e67ad..4d1cfd0 100644 --- a/plugin-src/client/index.js +++ b/plugin-src/client/index.js @@ -1,5 +1,6 @@ import * as React from 'react'; +import { DingtalkLogoGlyph, FeishuLogoGlyph, WeixinLogoGlyph } from './channel-logos.js'; import { DINGTALK_RPC_CHANNEL } from './channels/dingtalk/api.js'; import { DingtalkSettingsTab } from './channels/dingtalk/index.js'; import { FeishuSettingsTab } from './channels/feishu/index.js'; @@ -23,31 +24,17 @@ const CHANNELS = Object.freeze([ function WeixinLogo() { return h('span', { className: 'dim-logo dim-logoWeixin', 'aria-hidden': 'true' }, - h('svg', { viewBox: '0 0 24 24', focusable: 'false' }, - h('path', { - fill: 'currentColor', - d: 'M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z', - }), - )); + h(WeixinLogoGlyph)); } function FeishuLogo() { return h('span', { className: 'dim-logo dim-logoFeishu', 'aria-hidden': 'true' }, - h('svg', { viewBox: '0 0 24 24', focusable: 'false' }, - h('path', { fill: '#00D6B9', d: 'M7.2 4.5h7.6c1.2 0 2.1.55 2.7 1.58 1.05 1.8 1.55 3.45 1.58 4.95-2.04-.62-4.2-.15-6.22 1.45C11.3 9.7 9.42 7.04 7.2 4.5Z' }), - h('path', { fill: '#1456B8', d: 'M10.8 13.55c3.3-2.93 5.72-4.24 9.47-2.52-1.2 1.45-2.27 4.18-3.86 5.43-1.67 1.31-3.9.5-5.61-.64v-2.27Z' }), - h('path', { fill: '#3370FF', d: 'M4.4 8.35c3.47 3.61 7.25 6.1 10.33 5.7 1.06-.14 2.2-.72 3.4-1.72-1.04 2.65-2.6 4.8-5.06 6-2.46 1.2-5.56.52-7.42-.72A2.76 2.76 0 0 1 4.4 15.3V8.35Z' }), - )); + h(FeishuLogoGlyph)); } function DingtalkLogo() { return h('span', { className: 'dim-logo dim-logoDingtalk', 'aria-hidden': 'true' }, - h('svg', { viewBox: '0 0 48 48', focusable: 'false' }, - h('path', { - fill: 'currentColor', - 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', - }), - )); + h(DingtalkLogoGlyph)); } function ChannelLogo({ channel }) { @@ -62,7 +49,6 @@ export function IMSettingsTab({ dingtalkRpcCall, feishuRpcCall, weixinRpcCall }) return h('section', { className: 'dim-page', 'aria-label': 'IM机器人设置' }, h('header', { className: 'dim-title' }, h('p', null, '通过扫码把机器人接入 DeepSeek Harness'), - h('div', { className: 'dim-channelCount' }, `${CHANNELS.length} 个渠道`), ), h('div', { className: 'dim-layout' }, h('nav', { className: 'dim-rail', role: 'tablist', 'aria-label': 'IM 渠道' }, diff --git a/plugin-src/client/styles.js b/plugin-src/client/styles.js index 3303870..e4c5487 100644 --- a/plugin-src/client/styles.js +++ b/plugin-src/client/styles.js @@ -11,9 +11,8 @@ const CSS = String.raw` box-sizing: border-box; } .dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; } -.dim-title { display: flex; align-items: center; justify-content: space-between; gap: 22px; margin: 0 0 26px; } +.dim-title { margin: 0 0 26px; } .dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; } -.dim-channelCount { flex: none; padding: 6px 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 11px; white-space: nowrap; } .dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; } .dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 1px 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-line-border, #dfe1e5) transparent; } .dim-rail::-webkit-scrollbar { width: 4px; } diff --git a/test/channels/dingtalk/client-ui.test.mjs b/test/channels/dingtalk/client-ui.test.mjs index 6f2aa3c..da94966 100644 --- a/test/channels/dingtalk/client-ui.test.mjs +++ b/test/channels/dingtalk/client-ui.test.mjs @@ -22,7 +22,7 @@ test('QR guidance describes the complete official DingTalk authorization flow', assert.match(source, /如果钉钉提示尚未加入组织/); assert.match(source, /在授权页点击“一键创建新机器人”/); assert.match(source, /保持本页打开,等待机器人自动连接/); - assert.match(source, /官方授权页目前可能显示 OpenClaw 品牌/); + assert.doesNotMatch(source, /OpenClaw 品牌|ddt-brandNotice/); assert.match(source, /safeQrSource\(provision\.qrCodeDataUrl\)/); assert.doesNotMatch(source, /verificationUrl|打开备用链接/); assert.doesNotMatch(source, /dangerouslySetInnerHTML|window\.open\(/); @@ -63,11 +63,11 @@ test('the QR card responds to its plugin panel width instead of the browser view assert.match(styles, /\.ddt-qrCopy \{ min-width: 0; overflow-wrap: anywhere; \}/); }); -test('bot metrics stay in one compact three-column row at narrow widths', async () => { +test('bot metrics stay in one compact two-column row at narrow widths', async () => { const styles = await readFile(STYLES_URL, 'utf8'); assert.match( styles, - /\.ddt-metrics \{[^\n]*grid-template-columns: repeat\(3, minmax\(0, 1fr\)\)/, + /\.ddt-metrics \{[^\n]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/, ); assert.doesNotMatch( styles, diff --git a/test/client-ui.test.mjs b/test/client-ui.test.mjs index 216ccbd..de3e300 100644 --- a/test/client-ui.test.mjs +++ b/test/client-ui.test.mjs @@ -7,8 +7,18 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { IMSettingsTab } from '../plugin-src/client/index.js'; import { DINGTALK_ENDPOINTS } from '../plugin-src/client/channels/dingtalk/api.js'; +import { AccountCard as DingtalkAccountCard } from '../plugin-src/client/channels/dingtalk/index.js'; +import { + BotCard as FeishuBotCard, + FeishuSettingsTab, +} from '../plugin-src/client/channels/feishu/index.js'; +import { AccountCard as WeixinAccountCard } from '../plugin-src/client/channels/weixin/index.js'; const STYLES_URL = new URL('../plugin-src/client/styles.js', import.meta.url); +const FEISHU_STYLES_URL = new URL( + '../plugin-src/client/channels/feishu/styles.js', + import.meta.url, +); const CLIENT_BUNDLE_URL = new URL('../lib/client.js', import.meta.url); const DINGTALK_CLIENT_SOURCE_URL = new URL( '../plugin-src/client/channels/dingtalk/index.js', @@ -24,6 +34,7 @@ test('IM settings renders three compact logo channel tabs without enable switche assert.match(markup, /IM机器人/); assert.match(markup, /通过扫码把机器人接入 DeepSeek Harness/); + assert.doesNotMatch(markup, /\d+ 个渠道|dim-channelCount/); assert.match(markup, />微信); assert.match(markup, />飞书); assert.match(markup, />钉钉); @@ -49,6 +60,101 @@ test('the DingTalk QR card stacks within the narrow combined-channel panel', asy assert.match(styles, /\.dim-panel \.ddt-qrCopy \{ width: 100%; min-width: 0; overflow-wrap: anywhere; \}/); }); +test('Feishu bot cards place the application identifier under the bot name', async () => { + const styles = await readFile(FEISHU_STYLES_URL, 'utf8'); + const markup = renderToStaticMarkup(React.createElement(FeishuBotCard, { + connection: { + botId: 'bot-feishu-card', + state: 'connected', + connected: true, + bot: { + name: '今天是牢梁', + appIdMasked: 'cli_aaf4••••1234', + domain: 'feishu', + avatarUrl: 'https://example.com/custom-bot-avatar.png', + }, + health: { + summary: '长连接运行正常', + lastCheckedAt: '2026-08-15T07:30:49.000Z', + }, + }, + onReconnect() {}, + onRequestRemove() {}, + onConfirmRemove() {}, + onCancelRemove() {}, + })); + + assert.match(markup, /
]*>cli_aaf4••••1234<\/p>/); + assert.match(markup, /data-im-channel-logo="feishu"/); + assert.doesNotMatch(markup, /custom-bot-avatar/); + assert.equal((markup.match(/class="bxf-metric"/g) ?? []).length, 2); + assert.match(markup, />消息通道<[^]*>最近检查); + assert.doesNotMatch(markup, />应用标识<|>飞书机器人); + assert.match(styles, /\.bxf-statusGrid \{[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/); +}); + +test('Feishu keeps its three heading controls on one row without a plus icon', async () => { + const styles = await readFile(FEISHU_STYLES_URL, 'utf8'); + const markup = renderToStaticMarkup(React.createElement(FeishuSettingsTab, { + rpcCall: async () => ({ ok: true, value: {} }), + })); + + assert.match(markup, /class="bxf-button bxf-bindButton"[^>]*>扫码绑定机器人<\/span><\/button>/); + assert.doesNotMatch(markup, />添加机器人); + assert.match(styles, /\.bxf-headingTools \{[^}]*flex-wrap: nowrap;/); + assert.match(styles, /@container \(max-width: 620px\)[^]*\.bxf-headingTools \{ gap: 6px; \}/); + assert.doesNotMatch(styles, /\.bxf-headingTools \.bxf-button \{ margin-left: auto; \}/); +}); + +test('bot cards reuse the same channel brand logos as the channel rail', () => { + const railMarkup = renderToStaticMarkup(React.createElement(IMSettingsTab, { + feishuRpcCall: async () => ({ ok: true, value: {} }), + weixinRpcCall: async () => ({ ok: true, value: {} }), + dingtalkRpcCall: async () => ({ ok: true, value: {} }), + })); + const accountMarkup = renderToStaticMarkup(React.createElement(WeixinAccountCard, { + account: { + botId: 'bot-weixin-card', + state: 'connected', + connected: true, + bot: { name: '微信机器人', accountIdMasked: 'wxid••••1234' }, + stats: { messagesReceived: 2, messagesReplied: 2 }, + health: { summary: '长轮询运行正常', lastCheckedAt: '2026-08-15T07:30:49.000Z' }, + }, + onReconnect() {}, + onRequestRemove() {}, + onConfirmRemove() {}, + onCancelRemove() {}, + })); + + assert.match(railMarkup, /data-im-channel-logo="weixin"/); + assert.match(railMarkup, /data-im-channel-logo="feishu"/); + assert.match(accountMarkup, /class="dxw-avatar"[^]*data-im-channel-logo="weixin"/); + assert.equal((accountMarkup.match(/class="dxw-metric"/g) ?? []).length, 2); + assert.doesNotMatch(accountMarkup, /收到 \/ 回复/); +}); + +test('DingTalk bot cards omit the redundant received and replied metric', () => { + const markup = renderToStaticMarkup(React.createElement(DingtalkAccountCard, { + account: { + botId: 'bot-dingtalk-card', + state: 'connected', + connected: true, + bot: { name: '钉钉机器人', clientIdMasked: 'ding••••oioy' }, + stats: { messagesReceived: 2, messagesReplied: 2 }, + health: { summary: 'Stream 长连接运行正常', lastCheckedAt: '2026-08-15T07:30:49.000Z' }, + }, + onReconnect() {}, + onRequestRemove() {}, + onConfirmRemove() {}, + onCancelRemove() {}, + })); + + assert.equal((markup.match(/class="ddt-metric"/g) ?? []).length, 2); + assert.match(markup, />消息通道<[^]*>最近检查); + assert.doesNotMatch(markup, /收到 \/ 回复/); +}); + test('the bundled DingTalk channel has no local sender approval workflow', async () => { const [source, bundle] = await Promise.all([ readFile(DINGTALK_CLIENT_SOURCE_URL, 'utf8'),