window.__ModuleLoader__.load({ id: "@xmanrui/dsh-im", factory: (require) => { var module = { exports: {} }; var exports = module.exports; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // plugin-src/client/index.js var index_exports = {}; __export(index_exports, { IMSettingsTab: () => IMSettingsTab, apply: () => apply, inject: () => inject, name: () => name }); module.exports = __toCommonJS(index_exports); 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"; var DINGTALK_ENDPOINTS = Object.freeze({ status: "connection.status", beginProvisioning: "provision.begin", pollProvisioning: "provision.poll", cancelProvisioning: "provision.cancel", reconnectBot: "bot.reconnect", deleteBot: "bot.delete" }); var ACCOUNT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]); var SNAPSHOT_STATES = /* @__PURE__ */ new Set(["disconnected", "offline", "provisioning", "connected", "degraded"]); var PROVISION_STATES = /* @__PURE__ */ new Set([ "starting", "pending", "scanned", "authorizing", "creating", "connecting", "connected", "expired", "failed", "cancelled" ]); var HEALTH_STATES = /* @__PURE__ */ new Set(["healthy", "checking", "degraded", "offline"]); var FORBIDDEN_ERROR_FIELDS = /(client[_-]?secret|secret[_-]?ref|device[_-]?code|app[_-]?secret|access[_-]?token|token)/i; var QR_DATA_URL = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i; var MAX_QR_SOURCE_LENGTH = 2 * 1024 * 1024; function isRecord(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function optionalString(value, maxLength = 240) { if (typeof value !== "string") return void 0; const trimmed = value.trim(); return trimmed ? trimmed.slice(0, maxLength) : void 0; } function opaqueId(value) { const id = optionalString(value, 128); return id && /^[a-z\d_-]+$/i.test(id) ? id : void 0; } function timestamp(value) { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "string" && value.trim()) { const parsed = Date.parse(value); return Number.isNaN(parsed) ? void 0 : parsed; } return void 0; } function nonNegativeInteger(value) { const number = Number(value); return Number.isSafeInteger(number) && number >= 0 ? number : 0; } function clamp(value, min, max, fallback) { const number = Number(value); return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback; } function safeErrorCode(value, fallback) { const code = optionalString(value, 80); return code && /^[a-z][a-z\d_.:-]*$/i.test(code) && !FORBIDDEN_ERROR_FIELDS.test(code) ? code : fallback; } function sanitizeMessage(value, fallback) { const message = optionalString(value, 480) ?? fallback; if (FORBIDDEN_ERROR_FIELDS.test(message)) return fallback; return message.replace(/([=:]\s*)[^\s,;,。]+/g, "$1\u2022\u2022\u2022\u2022\u2022\u2022").slice(0, 240); } function normalizeError(value, fallbackCode, fallbackMessage) { if (!isRecord(value)) return void 0; return { code: safeErrorCode(value.code, fallbackCode), message: sanitizeMessage(value.message, fallbackMessage) }; } function unwrapRpcResult(result) { if (!isRecord(result) || typeof result.ok !== "boolean") { throw new Error("\u9489\u9489\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94"); } if (!result.ok) { const error = new Error(sanitizeMessage(result.error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25")); error.code = safeErrorCode(result.error?.code, "DINGTALK_RPC_ERROR"); throw error; } return result.value; } function safeQrSource(value) { if (typeof value !== "string" || value.length > MAX_QR_SOURCE_LENGTH) return void 0; return QR_DATA_URL.test(value) ? value : void 0; } function normalizeProvisioning(value, now = Date.now()) { const source = isRecord(value?.provisioning) ? value.provisioning : value; if (!isRecord(source)) throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u7ED1\u5B9A\u8FDB\u5EA6"); const attemptId = opaqueId(source.attemptId); if (!attemptId) throw new Error("\u9489\u9489\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1"); const reportedStatus = optionalString(source.status, 32) ?? optionalString(source.state, 32); const status = PROVISION_STATES.has(reportedStatus) ? reportedStatus : "failed"; const expiresAt = timestamp(source.expiresAt) ?? now + clamp(source.expiresIn, 1, 2 * 60 * 60, 10 * 60) * 1e3; const result = { attemptId, status, expiresAt, pollIntervalMs: clamp(source.pollIntervalMs, 1e3, 1e4, 3e3) }; const qrCodeDataUrl = safeQrSource(source.qrCodeDataUrl); if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl; if (opaqueId(source.botId)) result.botId = opaqueId(source.botId); if (source.alreadyConnected === true) result.alreadyConnected = true; const error = normalizeError( source.error, "DINGTALK_PROVISION_FAILED", "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210" ); if (error) result.error = error; return result; } function normalizeBot(value) { if (!isRecord(value)) return void 0; const botId = opaqueId(value.botId); if (!botId) return void 0; const bot = isRecord(value.bot) ? value.bot : {}; const connected = value.connected === true; const reportedState = ACCOUNT_STATES.has(value.state) ? value.state : "offline"; const state = connected ? "connected" : reportedState === "connected" ? "connecting" : reportedState; const health = isRecord(value.health) ? value.health : {}; const stats = isRecord(value.stats) ? value.stats : {}; return { botId, state, connected, configured: value.configured !== false, bot: { name: optionalString(bot.name, 100) ?? "\u9489\u9489\u673A\u5668\u4EBA", clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? "\u5DF2\u5B89\u5168\u4FDD\u5B58" }, health: { status: HEALTH_STATES.has(health.status) ? health.status : connected ? "healthy" : "offline", summary: optionalString(health.summary, 200) ?? (connected ? "\u9489\u9489 Stream \u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"), lastCheckedAt: timestamp(health.lastCheckedAt), lastConnectedAt: timestamp(health.lastConnectedAt) }, stats: { messagesReceived: nonNegativeInteger(stats.messagesReceived), messagesReplied: nonNegativeInteger(stats.messagesReplied) }, error: normalizeError(value.error, "DINGTALK_ACCOUNT_ERROR", "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA") ?? null }; } function normalizeSnapshot(value) { const source = isRecord(value?.snapshot) ? value.snapshot : value; if (!isRecord(source) || !Array.isArray(source.bots)) { throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868"); } const seen = /* @__PURE__ */ new Set(); const bots = source.bots.map(normalizeBot).filter((bot) => { if (!bot || seen.has(bot.botId)) return false; seen.add(bot.botId); return true; }); return { schemaVersion: Number.isSafeInteger(source.schemaVersion) ? source.schemaVersion : 1, revision: nonNegativeInteger(source.revision), state: SNAPSHOT_STATES.has(source.state) ? source.state : "offline", bots, totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length }, provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null }; } function presentError(error) { return { code: safeErrorCode(error?.code, "DINGTALK_ERROR"), message: sanitizeMessage(error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5") }; } function formatRemaining(milliseconds) { const seconds = Math.max(0, Math.ceil(Number(milliseconds) / 1e3) || 0); return `${String(Math.floor(seconds / 60)).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`; } // plugin-src/client/channels/dingtalk/index.js var React2 = __toESM(require("react"), 1); // plugin-src/client/channels/dingtalk/styles.js var DINGTALK_STYLE_ID = "xmanrui-dsh-dingtalk-settings"; var CSS = String.raw` .ddt-page { --ddt-accent: #1677ff; --ddt-accent-deep: #0958d9; --ddt-accent-wash: #eaf3ff; --ddt-success: var(--dsw-alias-state-success-primary, #20a162); --ddt-warning: var(--dsw-alias-state-warning-primary, #d97706); --ddt-error: var(--dsw-alias-state-error-primary, #d54941); width: 100%; max-width: 880px; display: flex; flex-direction: column; gap: 18px; padding: 2px 0 28px; container-type: inline-size; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } .ddt-page *, .ddt-page *::before, .ddt-page *::after { box-sizing: border-box; } .ddt-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } .ddt-headingCopy { min-width: 0; } .ddt-heading h2, .ddt-heading p, .ddt-card h3, .ddt-card h4, .ddt-card p { margin: 0; } .ddt-eyebrow { margin-bottom: 3px; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; font-weight: 650; letter-spacing: .08em; text-transform: uppercase; } .ddt-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .ddt-heading p { margin-top: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; } .ddt-tools, .ddt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } .ddt-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .ddt-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: #aeb3bb; } .ddt-dot[data-tone="success"] { background: var(--ddt-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ddt-success) 14%, transparent); } .ddt-dot[data-tone="warning"] { background: var(--ddt-warning); } .ddt-dot[data-tone="error"] { background: var(--ddt-error); } .ddt-button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; text-decoration: none; cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .15s ease; } .ddt-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .ddt-button:active:not(:disabled) { transform: translateY(1px); } .ddt-button:focus-visible { outline: 2px solid color-mix(in srgb, var(--ddt-accent) 70%, white); outline-offset: 2px; } .ddt-button:disabled { cursor: not-allowed; opacity: .55; } .ddt-button[data-kind="primary"] { color: #fff; border-color: var(--ddt-accent); background: var(--ddt-accent); } .ddt-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--ddt-accent-deep); background: var(--ddt-accent-deep); } .ddt-button[data-kind="danger"] { color: var(--ddt-error); } .ddt-button[data-kind="quiet"] { min-height: 30px; padding: 0 10px; border-color: transparent; background: transparent; } .ddt-card { overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } .ddt-cardBody { padding: 24px; } .ddt-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; } .ddt-empty h3 { margin: 8px 0; font-size: 18px; } .ddt-empty p { max-width: 560px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .ddt-empty .ddt-actions { margin-top: 20px; } .ddt-brandMark { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; color: #fff; background: linear-gradient(145deg, #2997ff, var(--ddt-accent)); box-shadow: 0 18px 45px rgb(22 119 255 / 23%); } .ddt-brandMark svg { filter: drop-shadow(0 3px 8px rgb(0 35 96 / 16%)); } .ddt-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; } .ddt-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; } .ddt-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 16px; background: #fff; } .ddt-qrFrame::before { content: ''; position: absolute; inset: 6px; border: 1px solid rgb(22 119 255 / 10%); border-radius: 11px; pointer-events: none; } .ddt-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; } .ddt-qrFallback { padding: 24px; color: #646a73; text-align: center; } .ddt-expired { position: absolute; inset: 0; display: grid; place-items: center; padding: 30px; color: #fff; text-align: center; font-weight: 650; white-space: pre-line; background: rgb(31 35 41 / 76%); backdrop-filter: blur(3px); } .ddt-countdown { width: min(270px, 100%); color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .ddt-countdownTop { display: flex; justify-content: space-between; margin-bottom: 6px; } .ddt-countdown strong { color: var(--dsw-alias-label-primary, #1f2329); font-variant-numeric: tabular-nums; } .ddt-progress { height: 4px; overflow: hidden; border-radius: 99px; background: #eef0f3; } .ddt-progress span { display: block; width: var(--ddt-progress); height: 100%; background: var(--ddt-accent); transition: width .2s linear; } .ddt-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 600; } .ddt-qrCopy { min-width: 0; overflow-wrap: anywhere; } .ddt-qrCopy h3 { margin: 9px 0 8px; font-size: 18px; } .ddt-qrCopy > p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .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-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; } .ddt-spinner { width: 24px; height: 24px; margin: 0 auto 13px; border: 3px solid #e6e8eb; border-top-color: var(--ddt-accent); border-radius: 50%; animation: ddt-spin .8s linear infinite; } .ddt-statusNotice, .ddt-inlineError { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--ddt-error) 28%, transparent); border-radius: 10px; color: var(--ddt-error); background: color-mix(in srgb, var(--ddt-error) 7%, transparent); font-size: 13px; } .ddt-inlineError { flex-direction: column; padding: 22px; } .ddt-inlineError h3 { font-size: 17px; } .ddt-inlineError p { line-height: 1.55; } .ddt-errorCode { font: 11px ui-monospace, SFMono-Regular, monospace; opacity: .8; } .ddt-listHeading { display: flex; align-items: center; justify-content: space-between; margin: 2px 0 9px; } .ddt-listHeading h3 { margin: 0; font-size: 14px; } .ddt-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; } .ddt-list { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; } .ddt-accountTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } .ddt-accountIdentity { min-width: 0; display: flex; align-items: center; gap: 12px; } .ddt-avatar { width: 42px; height: 42px; display: grid; place-items: center; flex: none; border-radius: 12px; color: #fff; background: linear-gradient(145deg, #2997ff, var(--ddt-accent)); } .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(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; } .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } .ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; } .ddt-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .ddt-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--ddt-error) 25%, transparent); background: color-mix(in srgb, var(--ddt-error) 5%, transparent); } .ddt-confirm strong { display: block; margin-bottom: 6px; font-size: 14px; } .ddt-confirm p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.55; } .ddt-confirm .ddt-actions { margin-top: 13px; } .ddt-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } @keyframes ddt-spin { to { transform: rotate(360deg); } } @container (max-width: 680px) { .ddt-heading { flex-direction: column; align-items: stretch; } .ddt-tools { width: 100%; flex-wrap: nowrap; gap: 6px; } .ddt-tools .ddt-badge { min-height: 34px; padding-inline: 8px; } .ddt-tools .ddt-button { flex: none; padding-inline: 10px; white-space: nowrap; } .ddt-empty { grid-template-columns: minmax(0, 1fr); } .ddt-brandMark { display: none; } .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } .ddt-qrColumn { width: 100%; min-width: 0; } .ddt-qrCopy { width: 100%; } .ddt-metrics { gap: 8px; } .ddt-metric { padding: 10px; } } @media (max-width: 720px) { .ddt-heading, .ddt-accountTop { flex-direction: column; align-items: stretch; } .ddt-heading p { white-space: normal; } .ddt-empty { grid-template-columns: minmax(0, 1fr); } .ddt-brandMark { display: none; } .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } .ddt-qrCopy { width: 100%; } .ddt-cardBody { padding: 20px; } } @media (prefers-reduced-motion: reduce) { .ddt-page *, .ddt-page *::before, .ddt-page *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } } `; function installDingtalkStyles() { if (typeof document === "undefined") return () => { }; const existing = document.querySelector(`style[data-plugin-css="${DINGTALK_STYLE_ID}"]`); if (existing) return () => { }; const style = document.createElement("style"); style.dataset.plugin = "@xmanrui/dsh-dingtalk"; style.dataset.pluginCss = DINGTALK_STYLE_ID; style.textContent = CSS; document.head.appendChild(style); return () => style.remove(); } // plugin-src/client/channels/dingtalk/index.js var h2 = React2.createElement; var ACTIVE_PROVISION_STATES = /* @__PURE__ */ new Set(["pending", "scanned", "authorizing", "creating", "connecting"]); function DingtalkIcon({ size = 28 }) { return h2("svg", { width: size, height: size, viewBox: "0 0 48 48", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false" }, 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 = React2.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) { return h2("button", { ...props, ref, type: "button", className: `ddt-button ${className}`.trim(), "data-kind": kind }, children); }); function Heading({ totals, adding, busy, onAdd, addButtonRef }) { return h2( "div", { className: "ddt-heading" }, h2( "div", { className: "ddt-headingCopy" }, 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") ), h2( "div", { className: "ddt-tools" }, h2(Button, { kind: "primary", className: "dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef }, adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u9489\u9489"), totals.configured > 0 ? h2( "div", { className: "ddt-badge dim-onlineBadge" }, h2("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) ) : null ) ); } function LoadingView() { return h2( "div", { className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, h2("div", { className: "ddt-spinner dim-spinner" }), h2("span", null, "\u6B63\u5728\u8BFB\u53D6\u9489\u9489\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView({ busy, onStart }) { return h2( "div", { className: "ddt-card dim-surfaceCard" }, h2( "div", { className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" }, h2( "div", { className: "dim-emptyCopy" }, h2( "div", { className: "ddt-stateLabel dim-stateLabel" }, h2("span", { className: "ddt-dot dim-stateDot" }), h2("span", null, "\u5C1A\u672A\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA") ), 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 dim-viewActions" }, h2( Button, { kind: "primary", onClick: onStart, disabled: busy }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u9489\u9489\u4E8C\u7EF4\u7801" ) ) ), h2( "div", { className: "ddt-brandMark dim-emptyBrand", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 68 }) ) ) ); } function QrPanel({ provision, now, busy, onRefresh, onCancel }) { 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); React2.useEffect(() => setImageFailed(false), [source]); return h2( "div", { className: "ddt-card dim-surfaceCard" }, h2( "div", { className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" }, h2( "div", { className: "ddt-qrColumn dim-qrColumn" }, h2( "div", { className: "ddt-qrFrame dim-qrFrame" }, 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) }) : h2("div", { className: "ddt-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"), expired ? h2("div", { className: "ddt-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), h2( "div", { className: "ddt-countdown dim-countdown" }, h2( "div", { className: "ddt-countdownTop dim-countdownTop" }, h2("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h2("strong", null, formatRemaining(remaining)) ), h2( "div", { className: "ddt-progress dim-progress", "aria-hidden": "true" }, h2("span", { style: { "--ddt-progress": `${progress}%` } }) ) ) ), h2( "div", { className: "ddt-qrCopy dim-qrCopy" }, h2( "div", { className: "ddt-stateLabel dim-stateLabel" }, h2("span", { className: "ddt-dot dim-stateDot", "data-tone": expired ? "error" : "warning" }), h2("span", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u7B49\u5F85\u9489\u9489\u626B\u7801\u6388\u6743") ), 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 dim-steps" }, 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") ), h2( "div", { className: "ddt-actions dim-viewActions" }, 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") ) ) ) ); } function ProgressPanel({ status, busy, onCancel }) { const connecting = status === "connecting"; const creating = status === "creating"; return h2( "div", { className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, h2("div", { className: "ddt-spinner dim-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 dim-viewActions", style: { justifyContent: "center", marginTop: 14 } }, h2(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u63A5\u5165") ) ); } function ProvisionError({ provision, busy, onRetry, onClose }) { const error = provision.error ?? { code: "DINGTALK_PROVISION_FAILED", message: "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210" }; return h2( "div", { className: "ddt-card dim-surfaceCard" }, h2( "div", { className: "ddt-inlineError dim-inlineError", role: "alert" }, 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 dim-viewActions" }, h2(Button, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), h2(Button, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) ) ); } function checkedTime(value) { if (!value) return "\u5C1A\u672A\u68C0\u67E5"; try { return new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(new Date(value)); } catch { return "\u521A\u521A"; } } function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { const cancelRef = React2.useRef(null); React2.useEffect(() => cancelRef.current?.focus(), []); return h2( "div", { className: "ddt-confirm dim-confirm", role: "alertdialog", "aria-label": `\u79FB\u9664${account.bot.name}`, onKeyDown: (event) => { if (event.key === "Escape" && !busy) onCancel(); } }, 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 dim-viewActions" }, 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" ) ) ); } 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"; const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA"; return h2( "article", { className: "ddt-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId }, h2( "div", { className: "ddt-cardBody dim-botCardBody" }, h2( "div", { className: "ddt-accountTop dim-botCardTop" }, h2( "div", { className: "ddt-accountIdentity dim-botIdentity" }, h2("div", { className: "ddt-avatar dim-botAvatar", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 29 })), h2( "div", { className: "dim-botName" }, h2("h3", { title: account.bot.name }, account.bot.name), h2("p", { title: account.bot.clientIdMasked }, account.bot.clientIdMasked) ) ), h2( "div", { className: "ddt-health dim-botHealth" }, h2("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }), h2("span", null, stateLabel) ) ), h2( "dl", { className: "ddt-metrics dim-botMetrics" }, h2( "div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6D88\u606F\u901A\u9053"), h2("dd", null, account.connected ? "Stream \u957F\u8FDE\u63A5" : "\u79BB\u7EBF") ), h2( "div", { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h2("dd", null, checkedTime(account.health.lastCheckedAt)) ) ), h2( "div", { className: "ddt-accountFooter dim-cardFooter" }, h2("div", { className: "ddt-summary dim-cardSummary" }, account.error?.message ?? account.health.summary), h2( "div", { className: "ddt-actions dim-cardActions" }, h2( Button, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), h2( Button, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165" ) ) ) ), removing ? h2(RemoveConfirmation, { account, busy: busy === "delete", onConfirm: onConfirmRemove, onCancel: onCancelRemove }) : null ); } function AccountList(props) { return h2( "section", { className: "dim-listSection" }, h2( "div", { className: "ddt-listHeading dim-listHeading" }, h2("h3", null, "\u5DF2\u63A5\u5165\u7684\u9489\u9489\u673A\u5668\u4EBA"), h2("span", null, `${props.bots.length} \u4E2A`) ), h2("ul", { className: "ddt-list dim-botList" }, props.bots.map((account) => h2( "li", { key: account.botId }, h2(AccountCard, { account, busy: props.busyByBot[account.botId], removing: props.removeTarget === account.botId, onReconnect: () => props.onReconnect(account), onRequestRemove: () => props.onRequestRemove(account), onConfirmRemove: () => props.onConfirmRemove(account), onCancelRemove: props.onCancelRemove }) ))) ); } var EMPTY_TOTALS = Object.freeze({ configured: 0, connected: 0 }); function DingtalkSettingsTab({ rpcCall }) { const [model, setModel] = React2.useState({ phase: "loading", bots: [], totals: EMPTY_TOTALS, revision: 0, error: null }); 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; statusRequestRef.current += 1; if (noticeFrameRef.current !== null) { window.cancelAnimationFrame(noticeFrameRef.current); noticeFrameRef.current = null; } if (focusFrameRef.current !== null) { window.cancelAnimationFrame(focusFrameRef.current); focusFrameRef.current = null; } }; }, []); React2.useEffect(() => installDingtalkStyles(), []); const announce = React2.useCallback((message) => { if (!mountedRef.current) return; if (noticeFrameRef.current !== null) { window.cancelAnimationFrame(noticeFrameRef.current); noticeFrameRef.current = null; } setNotice(""); if (message) { noticeFrameRef.current = window.requestAnimationFrame(() => { noticeFrameRef.current = null; if (mountedRef.current) setNotice(message); }); } }, []); const focusAddButton = React2.useCallback(() => { if (!mountedRef.current) return; if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current); focusFrameRef.current = window.requestAnimationFrame(() => { focusFrameRef.current = null; if (mountedRef.current) addButtonRef.current?.focus(); }); }, []); 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 = React2.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => { if (!mountedRef.current || signal?.aborted) return void 0; const requestId = statusRequestRef.current + 1; statusRequestRef.current = requestId; const canCommit = () => mountedRef.current && !signal?.aborted && statusRequestRef.current === requestId; if (!silent && canCommit()) { setModel((current) => ({ ...current, phase: "loading", error: null })); } try { const snapshot = normalizeSnapshot(await invoke(DINGTALK_ENDPOINTS.status, {}, signal)); if (!canCommit()) return void 0; setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision, error: null }); if (restoreProvisioning && snapshot.provisioning) { setProvision((current) => !current || current.attemptId === snapshot.provisioning.attemptId ? { ...current, ...snapshot.provisioning, durationMs: current?.durationMs ?? Math.max(1, snapshot.provisioning.expiresAt - Date.now()) } : current); } return snapshot; } catch (error) { if (error?.name === "AbortError" || !canCommit()) return void 0; setModel((current) => ({ ...current, phase: silent && current.phase === "ready" ? "ready" : "error", error: presentError(error) })); return void 0; } }, [invoke]); React2.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); React2.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let running = false; const timer = window.setInterval(async () => { if (running || controller.signal.aborted || !mountedRef.current) return; running = true; await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); running = false; }, 15e3); return () => { controller.abort(); window.clearInterval(timer); }; }, [loadStatus, model.phase]); 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 = React2.useCallback(async ({ replace = false } = {}) => { if (!mountedRef.current) return; setBusy(true); try { if (replace && provision?.attemptId) { await invoke(DINGTALK_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId }); if (!mountedRef.current) return; } setProvision({ status: "starting" }); const started = normalizeProvisioning(await invoke( DINGTALK_ENDPOINTS.beginProvisioning, { locale: "zh-CN" } )); if (!mountedRef.current) return; if (!started.qrCodeDataUrl) { throw new Error("\u9489\u9489\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u5B89\u5168\u7684\u4E8C\u7EF4\u7801"); } setNow(Date.now()); setProvision({ ...started, durationMs: Math.max(1, started.expiresAt - Date.now()) }); announce("\u9489\u9489\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u9489\u9489 App \u626B\u63CF\u3002"); } catch (error) { if (!mountedRef.current) return; setProvision({ attemptId: provision?.attemptId, status: "failed", error: presentError(error) }); } finally { if (mountedRef.current) setBusy(false); } }, [announce, invoke, provision?.attemptId]); const cancelProvisioning = React2.useCallback(async () => { if (!mountedRef.current) return; setBusy(true); try { if (provision?.attemptId && !["failed", "expired", "cancelled"].includes(provision.status)) { await invoke(DINGTALK_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId }); if (!mountedRef.current) return; } setProvision(null); announce("\u5DF2\u53D6\u6D88\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165\u3002"); focusAddButton(); } catch (error) { if (!mountedRef.current) return; setProvision((current) => ({ ...current, status: "failed", error: presentError(error) })); } finally { if (mountedRef.current) setBusy(false); } }, [announce, focusAddButton, invoke, provision?.attemptId, provision?.status]); React2.useEffect(() => { const attemptId = provision?.attemptId; if (!attemptId || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0; const controller = new AbortController(); let disposed = false; let timer = null; const canCommit = () => !disposed && !controller.signal.aborted && mountedRef.current; const schedule = (delay) => { if (!canCommit()) return; if (timer !== null) window.clearTimeout(timer); timer = window.setTimeout(() => { timer = null; if (canCommit()) void poll(); }, delay); }; const poll = async () => { try { const response = await invoke( DINGTALK_ENDPOINTS.pollProvisioning, { attemptId }, controller.signal ); if (!canCommit()) return; const result = normalizeProvisioning(response); if (result.status === "connected") { const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); if (!canCommit()) return; const account = result.botId ? snapshot?.bots.find((bot) => bot.botId === result.botId) : snapshot?.bots.find((bot) => bot.connected); if (!account?.connected) { setProvision((current) => current?.attemptId === attemptId ? { ...current, ...result, status: "connecting" } : current); schedule(result.pollIntervalMs); return; } setProvision(null); announce(result.alreadyConnected ? "\u8FD9\u4E2A\u9489\u9489\u673A\u5668\u4EBA\u5DF2\u7ECF\u63A5\u5165\u5E76\u4FDD\u6301\u5728\u7EBF\u3002" : "\u9489\u9489\u673A\u5668\u4EBA\u5DF2\u63A5\u5165\uFF0C\u53EF\u4EE5\u5F00\u59CB\u53D1\u9001\u6D88\u606F\u3002"); return; } if (!canCommit()) return; setProvision((current) => current?.attemptId === attemptId ? { ...current, ...result, durationMs: current.durationMs } : current); if (ACTIVE_PROVISION_STATES.has(result.status)) { schedule(result.pollIntervalMs); } } catch (error) { if (error?.name === "AbortError" || !canCommit()) return; setProvision((current) => current?.attemptId === attemptId ? { ...current, status: "failed", error: presentError(error) } : current); } }; schedule(provision.pollIntervalMs ?? 3e3); return () => { disposed = true; controller.abort(); if (timer !== null) window.clearTimeout(timer); timer = null; }; }, [announce, invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]); const setBotBusy = React2.useCallback((botId, operation) => { if (!mountedRef.current) return; setBusyByBot((current) => { const next = { ...current }; if (operation) next[botId] = operation; else delete next[botId]; return next; }); }, []); const runBotAction = React2.useCallback(async ({ account, operation, endpoint, payload, success }) => { if (!mountedRef.current) return void 0; setBotBusy(account.botId, operation); try { await invoke(endpoint, payload); if (!mountedRef.current) return void 0; const snapshot = await loadStatus({ silent: true, restoreProvisioning: false }); if (!mountedRef.current) return void 0; announce(typeof success === "function" ? success(snapshot) : success); return snapshot; } catch (error) { if (!mountedRef.current) return void 0; announce(`\u64CD\u4F5C\u5931\u8D25\uFF1A${presentError(error).message}`); return void 0; } finally { if (mountedRef.current) setBotBusy(account.botId, null); } }, [announce, invoke, loadStatus, setBotBusy]); 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 = React2.useCallback(async (account) => { const snapshot = await runBotAction({ account, operation: "delete", endpoint: DINGTALK_ENDPOINTS.deleteBot, payload: { botId: account.botId, confirm: true }, success: "\u9489\u9489\u673A\u5668\u4EBA\u53CA\u672C\u673A\u51ED\u636E\u5DF2\u79FB\u9664\u3002" }); if (snapshot && mountedRef.current) setRemoveTarget(null); }, [runBotAction]); let provisionView = null; if (provision?.status === "starting") { provisionView = h2( "div", { className: "ddt-card ddt-loading", "aria-busy": "true" }, 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 = h2(QrPanel, { provision, now, busy, onRefresh: () => void startProvisioning({ replace: true }), onCancel: () => void cancelProvisioning() }); } else if (["scanned", "authorizing", "creating", "connecting"].includes(provision?.status)) { provisionView = h2(ProgressPanel, { status: provision.status, busy, onCancel: () => void cancelProvisioning() }); } else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) { provisionView = h2(ProvisionError, { provision, busy, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onClose: () => void cancelProvisioning() }); } return h2( "section", { className: "ddt-page dim-channelPage", "aria-label": "\u9489\u9489\u8BBE\u7F6E" }, h2(Heading, { totals: model.totals, adding: Boolean(provision), busy, onAdd: () => void startProvisioning(), addButtonRef }), h2("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice), model.error && model.phase === "ready" ? h2("div", { className: "ddt-statusNotice dim-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 dim-surfaceCard" }, h2( "div", { className: "ddt-inlineError dim-inlineError", role: "alert" }, 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") ) ) : h2( React2.Fragment, null, provisionView, model.bots.length === 0 && !provision ? h2(EmptyView, { busy, onStart: () => void startProvisioning() }) : null, model.bots.length > 0 ? h2(AccountList, { bots: model.bots, busyByBot, removeTarget, onReconnect: (account) => void reconnect(account), onRequestRemove: (account) => setRemoveTarget(account.botId), onConfirmRemove: (account) => void remove(account), onCancelRemove: () => setRemoveTarget(null) }) : null ) ); } // plugin-src/client/channels/feishu/index.js var React4 = __toESM(require("react"), 1); // plugin-src/client/channels/feishu/api.js var FEISHU_RPC_CHANNEL = "/feishu"; var FEISHU_ENDPOINTS = Object.freeze({ status: "connection.status", beginProvisioning: "provision.begin", pollProvisioning: "provision.poll", cancelProvisioning: "provision.cancel", reconnectBot: "bot.reconnect", disconnectBot: "bot.disconnect", deleteBot: "bot.delete", // Kept for rolling upgrades. The multi-bot UI never calls these endpoints. testConnection: "connection.test", disconnect: "connection.disconnect" }); var CONNECTION_STATES = /* @__PURE__ */ new Set([ "disconnected", "offline", "provisioning", "connecting", "reconnecting", "connected", "error" ]); var POLL_STATES = /* @__PURE__ */ new Set([ "pending", "scanned", "connecting", "connected", "expired", "failed" ]); function isRecord2(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function optionalString2(value) { return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0; } function optionalTimestamp(value) { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "string" && value.length > 0) { const parsed = Date.parse(value); return Number.isNaN(parsed) ? void 0 : parsed; } return void 0; } function clamp2(value, min, max, fallback) { return typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback; } function unwrapRpcResult2(result) { if (!isRecord2(result) || typeof result.ok !== "boolean") { throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94"); } if (!result.ok) { const message = optionalString2(result.error?.message) ?? "\u98DE\u4E66\u670D\u52A1\u8BF7\u6C42\u5931\u8D25"; const error = new Error(message); error.code = optionalString2(result.error?.code) ?? "FEISHU_RPC_ERROR"; throw error; } return result.value; } function normalizeProvisioning2(value, now = Date.now()) { const source = isRecord2(value?.provisioning) ? value.provisioning : value; if (!isRecord2(source)) throw new Error("\u98DE\u4E66\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u4E8C\u7EF4\u7801\u4FE1\u606F"); const attemptId = optionalString2(source.attemptId) ?? optionalString2(source.provisioningId); const verificationUrl = optionalString2(source.verificationUrl); const qrCodeDataUrl = optionalString2(source.qrCodeDataUrl); if (!attemptId || !verificationUrl && !qrCodeDataUrl) { throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4E8C\u7EF4\u7801\u4FE1\u606F\u4E0D\u5B8C\u6574"); } const explicitExpiry = optionalTimestamp(source.expiresAt); const expireIn = clamp2(source.expireIn, 1, 60 * 60, 5 * 60); return { attemptId, verificationUrl, qrCodeDataUrl, expiresAt: explicitExpiry ?? now + expireIn * 1e3, pollIntervalMs: clamp2(source.pollIntervalMs, 800, 1e4, 1800) }; } function normalizeBot2(value) { const source = isRecord2(value) ? value : {}; return { name: optionalString2(source.name) ?? "\u98DE\u4E66\u673A\u5668\u4EBA", avatarUrl: optionalString2(source.avatarUrl), appIdMasked: optionalString2(source.appIdMasked), tenantName: optionalString2(source.tenantName), domain: source.domain === "lark" ? "lark" : "feishu", activated: typeof source.activated === "boolean" || typeof source.activated === "number" ? source.activated : void 0 }; } function normalizeHealth(value, connected = false) { const source = isRecord2(value) ? value : {}; const fallbackStatus = connected ? "healthy" : "offline"; const status = ["healthy", "degraded", "offline", "checking"].includes(source.status) ? source.status : fallbackStatus; return { status, summary: optionalString2(source.summary) ?? (connected ? "\u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "\u673A\u5668\u4EBA\u5C1A\u672A\u8FDE\u63A5"), lastCheckedAt: optionalTimestamp(source.lastCheckedAt), lastConnectedAt: optionalTimestamp(source.lastConnectedAt) }; } function normalizeError2(value) { if (!isRecord2(value)) return void 0; const message = optionalString2(value.message); if (!message) return void 0; return { message, code: optionalString2(value.code) }; } function authoritativeState(value, connected) { if (connected) return "connected"; const reported = CONNECTION_STATES.has(value) ? value : "disconnected"; if (reported === "connected" || reported === "connecting" || reported === "reconnecting") { return "connecting"; } if (reported === "error") return "error"; return "offline"; } function normalizeBotConnection(value, fallbackBotId) { if (!isRecord2(value)) throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u673A\u5668\u4EBA\u72B6\u6001"); const botId = optionalString2(value.botId) ?? optionalString2(fallbackBotId); if (!botId) throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u673A\u5668\u4EBA\u7F3A\u5C11 botId"); const connected = value.connected === true; return { botId, state: authoritativeState(value.state, connected), connected, configured: value.configured !== false, bot: normalizeBot2(value.bot), health: normalizeHealth(value.health, connected), error: normalizeError2(value.error) }; } function normalizeBotsSnapshot(value) { if (!isRecord2(value)) throw new Error("\u98DE\u4E66\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u8FDE\u63A5\u72B6\u6001"); let sourceBots = Array.isArray(value.bots) ? value.bots : []; if (sourceBots.length === 0 && value.configured === true) { sourceBots = [{ botId: optionalString2(value.botId) ?? "legacy-default", state: value.state, connected: value.connected, configured: true, bot: value.bot, health: value.health, error: value.error }]; } const seen = /* @__PURE__ */ new Set(); const bots = []; for (const source of sourceBots) { const bot = normalizeBotConnection(source); if (seen.has(bot.botId)) continue; seen.add(bot.botId); bots.push(bot); } const configured = bots.filter((bot) => bot.configured).length; const connected = bots.filter((bot) => bot.connected).length; const revision = Number.isSafeInteger(value.revision) && value.revision >= 0 ? value.revision : 0; const state = CONNECTION_STATES.has(value.state) ? value.state : "disconnected"; return { schemaVersion: value.schemaVersion === 2 ? 2 : 1, revision, state, bots, // Derive counts from the authoritative list so stale summary fields never // make the UI claim that an unavailable bot is online. totals: { configured, connected }, provisioning: value.provisioning ? normalizeProvisioning2(value.provisioning) : void 0, error: normalizeError2(value.error) }; } function normalizeConnectionSnapshot(value) { if (!isRecord2(value)) throw new Error("\u98DE\u4E66\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u8FDE\u63A5\u72B6\u6001"); const connected = value.connected === true; const reportedState = CONNECTION_STATES.has(value.state) ? value.state : "disconnected"; const state = connected ? "connected" : reportedState === "connected" ? "connecting" : reportedState; const snapshot = { state, configured: value.configured === true, bot: normalizeBot2(value.bot), health: normalizeHealth(value.health, connected), provisioning: void 0, errorMessage: optionalString2(value.error?.message) ?? optionalString2(value.message) }; if (value.provisioning) snapshot.provisioning = normalizeProvisioning2(value.provisioning); return snapshot; } function normalizePollResult(value) { if (!isRecord2(value)) throw new Error("\u98DE\u4E66\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u521B\u5EFA\u8FDB\u5EA6"); const status = POLL_STATES.has(value.status) ? value.status : POLL_STATES.has(value.state) ? value.state : void 0; if (!status) throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u672A\u77E5\u7684\u521B\u5EFA\u72B6\u6001"); const normalized = { status, botId: optionalString2(value.botId), message: optionalString2(value.error?.message) ?? optionalString2(value.message), connection: void 0, provisioning: void 0 }; if (value.provisioning) normalized.provisioning = normalizeProvisioning2(value.provisioning); if (status === "connected" && isRecord2(value.connection)) { normalized.connection = value.connection.botId ? normalizeBotConnection(value.connection) : normalizeConnectionSnapshot(value.connection); } return normalized; } function presentError2(error) { const raw = optionalString2(error?.message) ?? "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"; const message = raw.replace(/(client[_-]?secret|app[_-]?secret|secret|token)\s*[:=]\s*[^\s,;]+/gi, "$1=\u2022\u2022\u2022\u2022\u2022\u2022").slice(0, 240); return { message, code: optionalString2(error?.code) }; } function formatRemaining2(milliseconds) { const totalSeconds = Math.max(0, Math.ceil(milliseconds / 1e3)); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; } // plugin-src/client/lifecycle.js var React3 = __toESM(require("react"), 1); function createPollScheduler({ setTimeoutFn, clearTimeoutFn }) { let disposed = false; let timer; return { get disposed() { return disposed; }, schedule(callback, delayMs) { if (disposed) return false; if (timer !== void 0) clearTimeoutFn(timer); timer = setTimeoutFn(() => { timer = void 0; if (!disposed) void callback(); }, delayMs); return true; }, dispose() { if (disposed) return; disposed = true; if (timer !== void 0) clearTimeoutFn(timer); timer = void 0; } }; } function createAnimationFrameScheduler({ requestFrame, cancelFrame }) { let disposed = false; const frames = /* @__PURE__ */ new Set(); const keyedFrames = /* @__PURE__ */ new Map(); return { schedule(callback, key) { if (disposed) return false; const previous = key === void 0 ? void 0 : keyedFrames.get(key); if (previous !== void 0) { keyedFrames.delete(key); frames.delete(previous); cancelFrame(previous); } let frame; let completed = false; frame = requestFrame(() => { completed = true; if (frame !== void 0) frames.delete(frame); if (key !== void 0 && keyedFrames.get(key) === frame) keyedFrames.delete(key); if (!disposed) callback(); }); if (!completed) { frames.add(frame); if (key !== void 0) keyedFrames.set(key, frame); } return true; }, dispose() { if (disposed) return; disposed = true; for (const frame of frames) cancelFrame(frame); frames.clear(); keyedFrames.clear(); } }; } function useAnimationFrameScheduler() { const schedulerRef = React3.useRef(null); React3.useEffect(() => { const scheduler = createAnimationFrameScheduler({ requestFrame: (callback) => window.requestAnimationFrame(callback), cancelFrame: (frame) => window.cancelAnimationFrame(frame) }); schedulerRef.current = scheduler; return () => { scheduler.dispose(); if (schedulerRef.current === scheduler) schedulerRef.current = null; }; }, []); return React3.useCallback( (callback, key) => schedulerRef.current?.schedule(callback, key) ?? false, [] ); } // plugin-src/client/channels/feishu/styles.js var FEISHU_STYLE_ID = "beihuixinghe-dsh-feishu-settings"; var CSS2 = String.raw` .bxf-page { --bxf-accent: var(--dsw-alias-state-business-primary, #3370ff); --bxf-success: var(--dsw-alias-state-success-primary, #20a162); --bxf-warning: var(--dsw-alias-state-warning-primary, #d97706); --bxf-error: var(--dsw-alias-state-error-primary, #d54941); box-sizing: border-box; width: 100%; max-width: 860px; color: var(--dsw-alias-label-primary, #1f2329); display: flex; flex-direction: column; container-type: inline-size; gap: 18px; padding: 2px 0 24px; } .bxf-page *, .bxf-page *::before, .bxf-page *::after { box-sizing: border-box; } .bxf-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } .bxf-headingCopy { min-width: 0; } .bxf-heading h2, .bxf-heading p, .bxf-card h3, .bxf-card p { margin: 0; } .bxf-eyebrow { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; font-weight: 600; line-height: 18px; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 3px; } .bxf-heading h2 { font-size: 20px; line-height: 28px; font-weight: 650; letter-spacing: -.015em; } .bxf-heading p { max-width: 540px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; } .bxf-headingTools { width: 100%; flex: none; display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap; gap: 8px; } .bxf-totalBadge { min-height: 28px; display: inline-flex; align-items: baseline; gap: 3px; border-radius: 999px; padding: 4px 10px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f2f3f5); font-size: 11px; line-height: 16px; white-space: nowrap; } .bxf-totalBadge strong { color: var(--bxf-success); font-size: 13px; } .bxf-card { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 14px; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: var(--dsw-shadow-lv1, 0 3px 12px rgba(31, 35, 41, .05)); } .bxf-card::before { content: ""; pointer-events: none; position: absolute; inset: 0 0 auto; height: 88px; background: radial-gradient(circle at 86% -35%, color-mix(in srgb, var(--bxf-accent) 18%, transparent), transparent 68%); opacity: .85; } .bxf-cardBody { position: relative; padding: 24px; } .bxf-intro { min-height: 250px; display: grid; grid-template-columns: minmax(0, 1fr) 172px; gap: 32px; align-items: center; } .bxf-introCopy { max-width: 500px; } .bxf-stateLabel { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 600; line-height: 18px; margin-bottom: 13px; } .bxf-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #8f959e); box-shadow: 0 0 0 4px color-mix(in srgb, var(--dsw-alias-label-tertiary, #8f959e) 12%, transparent); } .bxf-dot[data-tone="success"] { background: var(--bxf-success); box-shadow: 0 0 0 4px color-mix(in srgb, var(--bxf-success) 13%, transparent); } .bxf-dot[data-tone="warning"] { background: var(--bxf-warning); box-shadow: 0 0 0 4px color-mix(in srgb, var(--bxf-warning) 13%, transparent); } .bxf-dot[data-tone="error"] { background: var(--bxf-error); box-shadow: 0 0 0 4px color-mix(in srgb, var(--bxf-error) 13%, transparent); } .bxf-intro h3 { font-size: 24px; line-height: 34px; font-weight: 650; letter-spacing: -.02em; } .bxf-introCopy > p { max-width: 490px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 14px; line-height: 23px; margin-top: 8px; } .bxf-note { display: flex; gap: 8px; align-items: flex-start; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: 18px; margin-top: 16px; } .bxf-note svg { flex: none; margin-top: 1px; } .bxf-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 22px; } .bxf-button { appearance: none; min-height: 36px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 8px; padding: 7px 13px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 550; line-height: 20px; text-decoration: none; cursor: pointer; transition: background .15s var(--ds-ease-in-out, ease), border-color .15s var(--ds-ease-in-out, ease), transform .15s var(--ds-ease-in-out, ease); } .bxf-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #f2f3f5); border-color: var(--dsw-alias-border-l1, #c9cdd4); } .bxf-button:active:not(:disabled) { transform: translateY(1px); } .bxf-button:focus-visible, .bxf-link:focus-visible { outline: 2px solid var(--bxf-accent); outline-offset: 2px; } .bxf-button:disabled { cursor: not-allowed; opacity: .55; } .bxf-button[data-kind="primary"] { border-color: var(--bxf-accent); color: #fff; background: var(--bxf-accent); box-shadow: 0 4px 12px color-mix(in srgb, var(--bxf-accent) 24%, transparent); } .bxf-button[data-kind="primary"]:hover:not(:disabled) { border-color: color-mix(in srgb, var(--bxf-accent) 86%, #000); background: color-mix(in srgb, var(--bxf-accent) 90%, #000); } .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)); } .bxf-markStage { position: relative; width: 156px; height: 156px; display: grid; place-items: center; justify-self: end; } .bxf-markStage::before, .bxf-markStage::after { content: ""; position: absolute; border-radius: 50%; } .bxf-markStage::before { inset: 12px; border: 1px solid color-mix(in srgb, var(--bxf-accent) 18%, var(--dsw-alias-border-l2, #dee0e3)); background: color-mix(in srgb, var(--bxf-accent) 4%, var(--dsw-alias-bg-layer-1, #fff)); } .bxf-markStage::after { inset: 0; border: 1px dashed color-mix(in srgb, var(--bxf-accent) 16%, transparent); animation: bxf-rotate 18s linear infinite; } .bxf-brandMark { position: relative; z-index: 1; width: 68px; height: 68px; display: grid; place-items: center; border-radius: 20px; color: #fff; background: var(--bxf-accent); box-shadow: 0 12px 28px color-mix(in srgb, var(--bxf-accent) 28%, transparent); } .bxf-qrLayout { display: grid; grid-template-columns: 236px minmax(0, 1fr); align-items: center; gap: 32px; } .bxf-qrColumn { min-width: 0; } .bxf-qrFrame { position: relative; width: 222px; height: 222px; display: grid; place-items: center; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 14px; padding: 13px; background: #fff; box-shadow: 0 8px 24px rgba(31, 35, 41, .07); } .bxf-qrFrame::before, .bxf-qrFrame::after { content: ""; position: absolute; width: 24px; height: 24px; border-color: var(--bxf-accent); border-style: solid; } .bxf-qrFrame::before { inset: -3px auto auto -3px; border-width: 2px 0 0 2px; border-radius: 5px 0 0; } .bxf-qrFrame::after { inset: auto -3px -3px auto; border-width: 0 2px 2px 0; border-radius: 0 0 5px; } .bxf-qrFrame img { width: 100%; height: 100%; display: block; object-fit: contain; } .bxf-qrFallback { width: 100%; height: 100%; display: grid; place-items: center; border-radius: 8px; color: var(--bxf-accent); background: #f7f9ff; text-align: center; padding: 20px; } .bxf-qrFallback span { display: block; color: #646a73; font-size: 12px; line-height: 18px; margin-top: 8px; } .bxf-expiredOverlay { position: absolute; inset: 10px; display: grid; place-items: center; border-radius: 9px; color: #1f2329; background: rgba(255, 255, 255, .94); backdrop-filter: blur(3px); font-size: 13px; font-weight: 600; text-align: center; } .bxf-countdown { width: 222px; color: var(--dsw-alias-label-tertiary, #8f959e); font-variant-numeric: tabular-nums; font-size: 11px; line-height: 17px; margin-top: 11px; } .bxf-countdownTop { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .bxf-progress { height: 3px; overflow: hidden; border-radius: 99px; background: var(--dsw-alias-bg-module-platform, #f2f3f5); margin-top: 6px; } .bxf-progress > span { display: block; width: var(--bxf-progress, 100%); height: 100%; border-radius: inherit; background: var(--bxf-accent); transition: width 1s linear; } .bxf-qrCopy h3 { font-size: 20px; line-height: 29px; font-weight: 650; } .bxf-qrCopy > p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 21px; margin-top: 7px; } .bxf-steps { counter-reset: bxf-step; display: flex; flex-direction: column; gap: 11px; margin: 20px 0 0; padding: 0; list-style: none; } .bxf-steps li { counter-increment: bxf-step; display: grid; grid-template-columns: 23px minmax(0, 1fr); align-items: start; gap: 9px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 19px; } .bxf-steps li::before { content: counter(bxf-step); width: 21px; height: 21px; display: grid; place-items: center; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 50%; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 10px; font-weight: 650; } .bxf-connecting { min-height: 292px; display: grid; place-items: center; text-align: center; padding: 36px 24px; } .bxf-connectingCopy { max-width: 430px; } .bxf-orbit { position: relative; width: 86px; height: 86px; display: grid; place-items: center; margin: 0 auto 22px; } .bxf-orbit::before, .bxf-orbit::after { content: ""; position: absolute; border-radius: 50%; } .bxf-orbit::before { inset: 3px; border: 1px solid color-mix(in srgb, var(--bxf-accent) 24%, transparent); animation: bxf-pulse 1.8s var(--ds-ease-in-out, ease) infinite; } .bxf-orbit::after { inset: 0; border: 2px solid transparent; border-top-color: var(--bxf-accent); animation: bxf-rotate 1.2s linear infinite; } .bxf-orbitCore { width: 50px; height: 50px; display: grid; place-items: center; border-radius: 16px; color: var(--bxf-accent); background: color-mix(in srgb, var(--bxf-accent) 9%, var(--dsw-alias-bg-layer-1, #fff)); } .bxf-connecting h3 { font-size: 20px; line-height: 29px; } .bxf-connecting p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 21px; margin-top: 7px; } .bxf-connectingCompact { min-height: 248px; } .bxf-inlineError { min-height: 190px; display: grid; grid-template-columns: 44px minmax(0, 1fr); align-content: center; gap: 15px; padding: 28px; } .bxf-inlineError h3 { font-size: 17px; line-height: 25px; margin: 0; } .bxf-inlineError p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 21px; margin-top: 5px; overflow-wrap: anywhere; } .bxf-listSection { display: flex; flex-direction: column; gap: 10px; } .bxf-listHeading { min-height: 28px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 2px; } .bxf-listHeading h3 { font-size: 14px; line-height: 22px; font-weight: 650; margin: 0; } .bxf-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; } .bxf-botList { display: flex; flex-direction: column; gap: 12px; margin: 0; padding: 0; list-style: none; } .bxf-botList > li { min-width: 0; } .bxf-botCard:focus { outline: none; } .bxf-botCard:focus-visible { outline: 2px solid var(--bxf-accent); outline-offset: 2px; } .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: 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 { 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(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; } .bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; } .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); } .bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; } .bxf-botActions .bxf-button { flex: none; white-space: nowrap; } .bxf-confirm { border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3); background: color-mix(in srgb, var(--bxf-error) 4%, var(--dsw-alias-bg-module-platform, #f7f8fa)); padding: 17px 24px 20px; } .bxf-confirm:focus { outline: none; } .bxf-confirm h4 { font-size: 13px; line-height: 20px; margin: 0; } .bxf-confirm p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 19px; margin: 4px 0 0; } .bxf-confirm .bxf-actions { margin-top: 12px; } .bxf-error { min-height: 252px; display: grid; grid-template-columns: 44px minmax(0, 1fr); align-content: center; gap: 15px; padding: 30px; } .bxf-errorIcon { width: 44px; height: 44px; display: grid; place-items: center; border-radius: 13px; color: var(--bxf-error); background: color-mix(in srgb, var(--bxf-error) 9%, transparent); } .bxf-error h3 { font-size: 17px; line-height: 25px; } .bxf-error p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 21px; margin-top: 5px; overflow-wrap: anywhere; } .bxf-errorCode { display: inline-block; color: var(--dsw-alias-label-tertiary, #8f959e); font-family: var(--ds-font-family-code, monospace); font-size: 11px; margin-top: 7px; } .bxf-statusNotice { display: flex; align-items: center; gap: 9px; border: 1px solid color-mix(in srgb, var(--bxf-warning) 28%, var(--dsw-alias-border-l2, #dee0e3)); border-radius: 10px; padding: 9px 11px; color: var(--dsw-alias-label-secondary, #646a73); background: color-mix(in srgb, var(--bxf-warning) 5%, var(--dsw-alias-bg-layer-1, #fff)); font-size: 12px; line-height: 18px; } .bxf-statusNotice > svg { flex: none; color: var(--bxf-warning); } .bxf-statusNotice > span { min-width: 0; flex: 1; overflow-wrap: anywhere; } .bxf-skeleton { min-height: 260px; padding: 28px; } .bxf-skeletonLine { height: 12px; border-radius: 999px; background: linear-gradient(90deg, var(--dsw-alias-bg-module-platform, #f2f3f5), color-mix(in srgb, var(--dsw-alias-label-tertiary, #8f959e) 10%, transparent), var(--dsw-alias-bg-module-platform, #f2f3f5)); background-size: 220% 100%; animation: bxf-shimmer 1.5s linear infinite; } .bxf-skeletonLine:nth-child(1) { width: 92px; } .bxf-skeletonLine:nth-child(2) { width: 44%; height: 22px; margin-top: 23px; } .bxf-skeletonLine:nth-child(3) { width: 72%; margin-top: 14px; } .bxf-skeletonLine:nth-child(4) { width: 58%; margin-top: 9px; } .bxf-skeletonBox { width: 138px; height: 38px; border-radius: 8px; background: var(--dsw-alias-bg-module-platform, #f2f3f5); margin-top: 28px; } .bxf-visuallyHidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } @keyframes bxf-rotate { to { transform: rotate(360deg); } } @keyframes bxf-pulse { 0%, 100% { transform: scale(.9); opacity: .45; } 50% { transform: scale(1.08); opacity: 1; } } @keyframes bxf-shimmer { to { background-position: -220% 0; } } @container (max-width: 620px) { .bxf-headingTools { gap: 6px; } .bxf-headingTools .bxf-totalBadge { padding-inline: 8px; } .bxf-headingTools .bxf-bindButton { padding-inline: 10px; } } @media (max-width: 680px) { .bxf-intro { grid-template-columns: minmax(0, 1fr); } .bxf-markStage { display: none; } .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } .bxf-qrCopy { width: 100%; } .bxf-statusGrid { grid-template-columns: minmax(0, 1fr); } .bxf-connectedTop { align-items: flex-start; flex-direction: column; } .bxf-inlineError { grid-template-columns: minmax(0, 1fr); padding: 20px; } .bxf-statusNotice { align-items: flex-start; flex-wrap: wrap; } .bxf-cardBody { padding: 20px; } } @media (prefers-reduced-motion: reduce) { .bxf-page *, .bxf-page *::before, .bxf-page *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; } } `; function installFeishuStyles() { if (typeof document === "undefined") { return () => { }; } const existing = document.querySelector( `style[data-plugin-css="${FEISHU_STYLE_ID}"]` ); if (existing) { return () => { }; } const style = document.createElement("style"); style.dataset.plugin = "@xmanrui/dsh-feishu"; style.dataset.pluginCss = FEISHU_STYLE_ID; style.textContent = CSS2; document.head.appendChild(style); return () => { style.remove(); }; } // plugin-src/client/channels/feishu/index.js var h3 = React4.createElement; function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) { return h3("svg", { width: size, height: size, viewBox, fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", className }, children); } function RobotIcon({ size = 26 }) { return h3( SvgIcon, { size }, h3("rect", { x: "5", y: "7.5", width: "14", height: "11", rx: "4", stroke: "currentColor", strokeWidth: "1.7" }), 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", strokeLinecap: "round" }) ); } function AlertIcon({ size = 22 }) { return h3( SvgIcon, { size }, h3("path", { d: "M12 3.4 21 19H3L12 3.4Z", stroke: "currentColor", strokeWidth: "1.7", strokeLinejoin: "round" }), h3("path", { d: "M12 9v4.4M12 16.6v.01", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round" }) ); } function QrIcon({ size = 58 }) { 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 = 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, h3("span", null, children)); }); 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 h3( "div", { className: "bxf-heading" }, h3( "div", { className: "bxf-headingTools" }, h3(Button3, { kind: "primary", size: "small", className: "bxf-bindButton dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef, "aria-busy": busy ? "true" : void 0 }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u673A\u5668\u4EBA"), hasBots ? h3("div", { className: "bxf-totalBadge dim-onlineBadge", "aria-label": `\u5DF2\u63A5\u5165 ${totals.configured} \u4E2A\u673A\u5668\u4EBA\uFF0C\u5176\u4E2D ${totals.connected} \u4E2A\u5728\u7EBF` }, h3("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)) : null ) ); } function LoadingView2() { return h3( "div", { className: "bxf-card dim-surfaceCard dim-loadingView", "aria-busy": "true", "aria-label": "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u5217\u8868" }, h3("div", { className: "dim-spinner", "aria-hidden": "true" }), h3("span", null, "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView2({ onStart, busy }) { return h3( "div", { className: "bxf-card dim-surfaceCard" }, h3( "div", { className: "bxf-cardBody bxf-intro dim-surfaceBody dim-emptyView" }, h3( "div", { className: "bxf-introCopy dim-emptyCopy" }, h3( "div", { className: "bxf-stateLabel dim-stateLabel" }, h3("span", { className: "bxf-dot dim-stateDot" }), h3("span", null, "\u5C1A\u672A\u63A5\u5165\u673A\u5668\u4EBA") ), 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 dim-viewActions" }, h3(Button3, { kind: "primary", onClick: onStart, disabled: busy, "aria-busy": busy ? "true" : void 0 }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u98DE\u4E66\u4E8C\u7EF4\u7801") ) ), h3("div", { className: "bxf-markStage dim-emptyBrand", "aria-hidden": "true" }, h3(BrandMark)) ) ); } function safeVerificationHref(value) { if (!value) return void 0; try { const url = new URL(value); return url.protocol === "https:" ? url.toString() : void 0; } catch { return void 0; } } function safeQrSource2(value) { if (!value) return void 0; 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] = 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)); React4.useEffect(() => setImageFailed(false), [qrSource]); return h3( "div", { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, h3( "div", { className: "bxf-cardBody bxf-qrLayout dim-surfaceBody dim-qrLayout" }, h3( "div", { className: "bxf-qrColumn dim-qrColumn" }, h3( "div", { className: "bxf-qrFrame dim-qrFrame" }, 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) }) : h3( "div", { className: "bxf-qrFallback dim-qrFallback" }, h3("div", null, h3(QrIcon), h3("span", null, "\u4E8C\u7EF4\u7801\u672A\u5C31\u7EEA\uFF0C\u8BF7\u6253\u5F00\u6388\u6743\u94FE\u63A5")) ), expired ? h3( "div", { className: "bxf-expiredOverlay dim-qrExpired", role: "status" }, h3("div", null, "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548", h3("br"), "\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u626B\u7801") ) : null ), h3( "div", { className: "bxf-countdown dim-countdown", "aria-label": expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : `\u4E8C\u7EF4\u7801\u5269\u4F59 ${formatRemaining2(remaining)}` }, h3( "div", { className: "bxf-countdownTop dim-countdownTop", "aria-hidden": "true" }, h3("span", null, expired ? "\u7B49\u5F85\u5237\u65B0" : "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h3("strong", null, formatRemaining2(remaining)) ), h3( "div", { className: "bxf-progress dim-progress", "aria-hidden": "true" }, h3("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } }) ) ) ), h3( "div", { className: "bxf-qrCopy dim-qrCopy" }, h3( "div", { className: "bxf-stateLabel dim-stateLabel" }, h3("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }), h3("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA") ), 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 dim-steps" }, 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") ), h3( "div", { className: "bxf-actions dim-viewActions" }, expired ? h3(Button3, { kind: "primary", onClick: onRefresh, disabled: busy }, 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" }, h3("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null, !expired ? h3(Button3, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") ) ) ) ); } function ProvisionProgress({ phase, onCancel, busy }) { const connecting = phase === "connecting"; return h3( "div", { className: "bxf-card bxf-provisionCard dim-surfaceCard dim-loadingView", "aria-busy": "true" }, h3("div", { className: "dim-spinner", "aria-hidden": "true" }), 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 dim-viewActions", style: { justifyContent: "center" } }, h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") ) : null ); } function ProvisionError2({ error, onRetry, onCancel, busy }) { return h3( "div", { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, h3( "div", { className: "bxf-inlineError dim-inlineError", role: "alert" }, h3( "div", null, 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 dim-viewActions" }, h3( Button3, { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801" ), h3(Button3, { onClick: onCancel, disabled: busy }, "\u5173\u95ED") ) ) ) ); } var HEALTH_LABELS = { connected: "\u8FD0\u884C\u6B63\u5E38", connecting: "\u6B63\u5728\u8FDE\u63A5", offline: "\u8FDE\u63A5\u4E2D\u65AD", error: "\u9700\u8981\u5904\u7406" }; function formatCheckedTime(timestamp3) { if (!timestamp3) return "\u5C1A\u672A\u68C0\u67E5"; try { return new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(new Date(timestamp3)); } catch { return "\u521A\u521A"; } } function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) { 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}`; React4.useEffect(() => cancelRef.current?.focus(), []); return h3( "div", { className: "bxf-confirm dim-confirm", role: "alertdialog", "aria-labelledby": titleId, "aria-describedby": descriptionId, onKeyDown: (event) => { if (event.key === "Escape" && !busy) { event.preventDefault(); onCancel(); } } }, 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" ), h3( "div", { className: "bxf-actions dim-viewActions" }, 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" ) ) ); } function BotCard({ connection, busy, actionError, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove, cardRef, removeButtonRef }) { const { bot, health, state, connected } = connection; const stateForDisplay = busy === "reconnect" ? "connecting" : state; 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, "-")}`; return h3( "article", { className: "bxf-card bxf-botCard dim-botCard", "aria-labelledby": titleId, "data-bot-id": connection.botId, tabIndex: -1, ref: cardRef }, h3( "div", { className: "bxf-cardBody dim-botCardBody" }, h3( "div", { className: "bxf-connectedTop dim-botCardTop" }, h3( "div", { className: "bxf-botIdentity dim-botIdentity" }, h3( "div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" }, h3(FeishuLogoGlyph, { size: 34 }) ), h3( "div", { className: "bxf-botName dim-botName" }, 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") ) ), h3( "div", { className: "bxf-healthPill dim-botHealth", "data-health": stateForDisplay }, h3("span", { className: "bxf-dot dim-healthDot", "data-tone": tone }), h3("span", null, HEALTH_LABELS[stateForDisplay] ?? "\u72B6\u6001\u672A\u77E5") ) ), h3( "dl", { className: "bxf-statusGrid dim-botMetrics" }, h3( "div", { className: "bxf-metric dim-botMetric" }, h3("dt", null, "\u6D88\u606F\u901A\u9053"), h3("dd", null, connected ? "\u957F\u8FDE\u63A5" : stateForDisplay === "connecting" ? "\u8FDE\u63A5\u4E2D" : "\u5DF2\u65AD\u5F00") ), h3( "div", { className: "bxf-metric dim-botMetric" }, h3("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h3("dd", null, formatCheckedTime(health.lastCheckedAt)) ) ), h3( "div", { className: "bxf-connectedFooter dim-cardFooter" }, h3( "div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 }, summary ), h3( "div", { className: "bxf-actions bxf-botActions dim-cardActions" }, h3(Button3, { className: "dim-cardAction", 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"), h3(Button3, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy), ref: removeButtonRef, "aria-label": `\u4ECE DeepSeek Harness \u79FB\u9664${bot.name}` }, "\u79FB\u9664\u63A5\u5165") ) ) ), removing ? h3(RemoveConfirmation2, { bot: connection, busy: busy === "delete", onConfirm: onConfirmRemove, onCancel: onCancelRemove }) : null ); } function BotList(props) { return h3( "section", { className: "bxf-listSection dim-listSection", "aria-labelledby": "bxf-bot-list-title" }, h3( "div", { className: "bxf-listHeading dim-listHeading" }, h3("h3", { id: "bxf-bot-list-title" }, "\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA"), h3("span", null, `${props.bots.length} \u4E2A`) ), h3( "ul", { className: "bxf-botList dim-botList", role: "list" }, props.bots.map((bot) => h3( "li", { key: bot.botId }, h3(BotCard, { connection: bot, busy: props.busyByBot[bot.botId], actionError: props.errorsByBot[bot.botId], removing: props.removeTargetId === bot.botId, onReconnect: () => props.onReconnect(bot), onRequestRemove: () => props.onRequestRemove(bot), onConfirmRemove: () => props.onConfirmRemove(bot), onCancelRemove: props.onCancelRemove, cardRef: (node) => props.setCardRef(bot.botId, node), removeButtonRef: (node) => props.setRemoveButtonRef(bot.botId, node) }) )) ) ); } function PageError({ error, onRetry, busy }) { return h3( "div", { className: "bxf-card dim-surfaceCard" }, h3( "div", { className: "bxf-error dim-inlineError", role: "alert" }, h3( "div", null, 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 dim-viewActions" }, h3( Button3, { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u8BFB\u53D6" ) ) ) ) ); } var EMPTY_TOTALS2 = Object.freeze({ configured: 0, connected: 0 }); function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = false, now = Date.now() } = {}) { if (snapshot.revision > 0 && current.revision > snapshot.revision) return current; let provisioning = current.provisioning; if (!provisioning && restoreProvisioning && snapshot.provisioning) { provisioning = { phase: snapshot.state === "connecting" ? "connecting" : "qr", ...snapshot.provisioning, durationMs: Math.max(1, snapshot.provisioning.expiresAt - now), expired: snapshot.provisioning.expiresAt <= now }; } return { ...current, phase: "ready", revision: snapshot.revision, bots: snapshot.bots, totals: snapshot.totals, provisioning, pageError: null, statusError: null }; } function FeishuSettingsTab({ rpcCall }) { const [model, setModel] = React4.useState({ phase: "loading", revision: 0, bots: [], totals: EMPTY_TOTALS2, provisioning: null, pageError: null, statusError: 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 = React4.useCallback((message) => { setAnnouncement(""); scheduleAnimationFrame(() => { if (message) setAnnouncement(message); }, "announcement"); }, [scheduleAnimationFrame]); const invoke = React4.useCallback(async (endpoint, payload = {}, signal) => { return unwrapRpcResult2(await rpcCall(endpoint, payload, signal)); }, [rpcCall]); const mergeSnapshot = React4.useCallback((snapshot, { restoreProvisioning = false } = {}) => { const now2 = Date.now(); setModel((current) => mergeFeishuSnapshotState( current, snapshot, { restoreProvisioning, now: now2 } )); }, []); const loadStatus = React4.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => { if (!silent) setPageBusy(true); try { const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.status, {}, signal)); if (signal?.aborted) return void 0; mergeSnapshot(snapshot, { restoreProvisioning }); return snapshot; } catch (error) { if (signal?.aborted || error?.name === "AbortError") return void 0; const presented = presentError2(error); setModel((current) => current.phase === "loading" || !silent ? { ...current, phase: "error", pageError: presented } : { ...current, statusError: presented }); return void 0; } finally { if (!silent && !signal?.aborted) setPageBusy(false); } }, [invoke, mergeSnapshot]); React4.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); React4.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let inFlight = false; const timer = window.setInterval(async () => { if (inFlight) return; inFlight = true; await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); inFlight = false; }, 15e3); return () => { controller.abort(); window.clearInterval(timer); }; }, [loadStatus, model.phase]); React4.useEffect(() => { if (!focusBotId) return; const node = cardRefs.current.get(focusBotId); if (!node) return; node.scrollIntoView?.({ block: "nearest", behavior: "smooth" }); node.focus({ preventScroll: true }); setFocusBotId(null); }, [focusBotId, model.bots]); const startProvisioning = React4.useCallback(async ({ replace = false } = {}) => { setProvisionBusy(true); announce(""); const previousAttemptId = model.provisioning?.attemptId; setModel((current) => ({ ...current, phase: current.phase === "loading" ? "ready" : current.phase, provisioning: { phase: "creating" } })); try { if (replace && previousAttemptId) { await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId: previousAttemptId }); } const provision2 = normalizeProvisioning2(await invoke( FEISHU_ENDPOINTS.beginProvisioning, { locale: "zh-CN" } )); const timestamp3 = Date.now(); setNow(timestamp3); setModel((current) => ({ ...current, provisioning: { phase: "qr", ...provision2, durationMs: Math.max(1, provision2.expiresAt - timestamp3), expired: false } })); announce("\u6388\u6743\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002"); } catch (error) { setModel((current) => ({ ...current, provisioning: { phase: "error", error: presentError2(error) } })); } finally { setProvisionBusy(false); } }, [announce, invoke, model.provisioning?.attemptId]); const cancelProvisioning = React4.useCallback(async () => { const attemptId = model.provisioning?.attemptId; setProvisionBusy(true); try { if (attemptId) await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId }); setModel((current) => ({ ...current, provisioning: null })); announce("\u5DF2\u53D6\u6D88\u6DFB\u52A0\u673A\u5668\u4EBA\u3002"); await loadStatus({ silent: true, restoreProvisioning: false }); scheduleAnimationFrame(() => addButtonRef.current?.focus(), "focus"); } catch (error) { setModel((current) => ({ ...current, provisioning: { phase: "error", attemptId, error: presentError2(error) } })); } finally { setProvisionBusy(false); } }, [announce, invoke, loadStatus, model.provisioning?.attemptId, scheduleAnimationFrame]); const countdownAttemptId = model.provisioning?.attemptId; const countdownPhase = model.provisioning?.phase; const countdownExpiresAt = model.provisioning?.expiresAt; const countdownExpired = model.provisioning?.expired; React4.useEffect(() => { if (!countdownAttemptId || countdownPhase !== "qr" || countdownExpired) return void 0; const tick = () => { const timestamp3 = Date.now(); setNow(timestamp3); if (timestamp3 >= countdownExpiresAt) { setModel((current) => current.provisioning?.attemptId === countdownAttemptId ? { ...current, provisioning: { ...current.provisioning, expired: true } } : current); } }; tick(); const timer = window.setInterval(tick, 1e3); return () => window.clearInterval(timer); }, [countdownAttemptId, countdownPhase, countdownExpiresAt, countdownExpired]); React4.useEffect(() => { const provision2 = model.provisioning; if (!provision2 || !["qr", "connecting"].includes(provision2.phase) || !provision2.attemptId || provision2.expired) return void 0; const controller = new AbortController(); const timer = window.setTimeout(async () => { try { const result = normalizePollResult(await invoke( FEISHU_ENDPOINTS.pollProvisioning, { attemptId: provision2.attemptId }, controller.signal )); if (result.status === "connected") { const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); const newBot = snapshot?.bots.find((bot) => bot.botId === result.botId); if (!snapshot) { throw new Error("\u673A\u5668\u4EBA\u5DF2\u7ECF\u521B\u5EFA\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FDE\u63A5\u72B6\u6001"); } if (!newBot?.connected) { setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "connecting" } } : current); return; } setModel((current) => ({ ...current, provisioning: null })); announce(newBot ? `${newBot.bot.name}\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5728\u98DE\u4E66\u4E2D\u5F00\u59CB\u804A\u5929\u3002` : "\u65B0\u98DE\u4E66\u673A\u5668\u4EBA\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5F00\u59CB\u804A\u5929\u3002"); if (result.botId) setFocusBotId(result.botId); return; } if (result.status === "failed") { const error = new Error(result.message ?? "\u98DE\u4E66\u5E94\u7528\u521B\u5EFA\u5931\u8D25"); error.code = "FEISHU_PROVISION_FAILED"; throw error; } if (result.status === "expired") { setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "qr", expired: true } } : current); return; } setModel((current) => { if (current.provisioning?.attemptId !== provision2.attemptId) return current; const next = result.provisioning ?? current.provisioning; return { ...current, provisioning: { ...current.provisioning, ...next, phase: ["scanned", "connecting"].includes(result.status) ? "connecting" : "qr" } }; }); } catch (error) { if (error?.name === "AbortError") return; setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { phase: "error", attemptId: provision2.attemptId, error: presentError2(error) } } : current); } }, provision2.pollIntervalMs); return () => { controller.abort(); window.clearTimeout(timer); }; }, [announce, invoke, loadStatus, model.provisioning]); const setBotBusy = React4.useCallback((botId, value) => { setBusyByBot((current) => { const next = { ...current }; if (value) next[botId] = value; else delete next[botId]; return next; }); }, []); const setBotError = React4.useCallback((botId, error) => { setErrorsByBot((current) => { const next = { ...current }; if (error) next[botId] = presentError2(error); else delete next[botId]; return next; }); }, []); const reconnectOneBot = React4.useCallback(async (connection) => { const { botId, bot } = connection; setBotBusy(botId, "reconnect"); setBotError(botId, null); try { const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId })); mergeSnapshot(snapshot); const refreshed = snapshot.bots.find((item) => item.botId === botId); if (!refreshed?.connected) { const error = new Error( refreshed?.error?.message ?? refreshed?.health.summary ?? "\u673A\u5668\u4EBA\u4ECD\u672A\u8FDE\u63A5" ); error.code = refreshed?.error?.code ?? "FEISHU_BOT_OFFLINE"; throw error; } announce(connection.connected ? `${bot.name}\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002` : `${bot.name}\u5DF2\u91CD\u65B0\u8FDE\u63A5\u3002`); } catch (error) { setBotError(botId, error); announce(`${bot.name}\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u673A\u5668\u4EBA\u72B6\u6001\u3002`); } finally { setBotBusy(botId, null); } }, [announce, invoke, mergeSnapshot, setBotBusy, setBotError]); const requestRemove = React4.useCallback((connection) => { setRemoveTargetId(connection.botId); }, []); const cancelRemove = React4.useCallback(() => { const botId = removeTargetId; setRemoveTargetId(null); scheduleAnimationFrame(() => removeButtonRefs.current.get(botId)?.focus(), "focus"); }, [removeTargetId, scheduleAnimationFrame]); const confirmRemove = React4.useCallback(async (connection) => { const { botId, bot } = connection; setBotBusy(botId, "delete"); setBotError(botId, null); try { await invoke(FEISHU_ENDPOINTS.deleteBot, { botId, confirm: true }); setRemoveTargetId(null); setModel((current) => { const bots = current.bots.filter((item) => item.botId !== botId); return { ...current, bots, totals: { configured: bots.length, connected: bots.filter((item) => item.connected).length } }; }); announce(`${bot.name}\u5DF2\u4ECE\u6B64 DeepSeek Harness \u79FB\u9664\uFF1B\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u5E94\u7528\u672A\u88AB\u5220\u9664\u3002`); await loadStatus({ silent: true }); scheduleAnimationFrame(() => addButtonRef.current?.focus(), "focus"); } catch (error) { setBotError(botId, error); announce(`${bot.name}\u79FB\u9664\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002`); } finally { setBotBusy(botId, null); } }, [announce, invoke, loadStatus, scheduleAnimationFrame, setBotBusy, setBotError]); const provision = model.provisioning; let provisionContent = null; if (provision?.phase === "creating") { provisionContent = h3(ProvisionProgress, { phase: "creating", busy: provisionBusy }); } else if (provision?.phase === "qr") { provisionContent = h3(QrPane, { provision, now, onRefresh: () => void startProvisioning({ replace: true }), onCancel: () => void cancelProvisioning(), busy: provisionBusy || model.phase !== "ready" }); } else if (provision?.phase === "connecting") { provisionContent = h3(ProvisionProgress, { phase: "connecting", onCancel: () => void cancelProvisioning(), busy: provisionBusy }); } else if (provision?.phase === "error") { provisionContent = h3(ProvisionError2, { error: provision.error, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onCancel: () => void cancelProvisioning(), busy: provisionBusy }); } const setCardRef = React4.useCallback((botId, node) => { if (node) cardRefs.current.set(botId, node); else cardRefs.current.delete(botId); }, []); const setRemoveButtonRef = React4.useCallback((botId, node) => { if (node) removeButtonRefs.current.set(botId, node); else removeButtonRefs.current.delete(botId); }, []); return h3( "section", { className: "bxf-page dim-channelPage", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" }, h3(Heading2, { totals: model.totals, onAdd: () => void startProvisioning(), adding: Boolean(provision), busy: provisionBusy, addButtonRef }), h3("div", { className: "bxf-visuallyHidden", role: "status", "aria-live": "polite", "aria-atomic": "true" }, announcement), model.statusError ? h3( "div", { className: "bxf-statusNotice dim-statusNotice", role: "status" }, 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" ? h3(LoadingView2) : model.phase === "error" ? h3(PageError, { error: model.pageError ?? { message: "\u65E0\u6CD5\u8BFB\u53D6\u8FDE\u63A5\u72B6\u6001" }, onRetry: () => void loadStatus(), busy: pageBusy }) : h3( React4.Fragment, null, provisionContent, model.bots.length === 0 && !provision ? h3(EmptyView2, { onStart: () => void startProvisioning(), busy: provisionBusy }) : null, model.bots.length > 0 ? h3(BotList, { bots: model.bots, busyByBot, errorsByBot, removeTargetId, onReconnect: (bot) => void reconnectOneBot(bot), onRequestRemove: requestRemove, onConfirmRemove: (bot) => void confirmRemove(bot), onCancelRemove: cancelRemove, setCardRef, setRemoveButtonRef }) : null ) ); } // plugin-src/client/channels/weixin/index.js var React5 = __toESM(require("react"), 1); // plugin-src/client/channels/weixin/api.js var WEIXIN_RPC_CHANNEL = "/weixin"; var WEIXIN_ENDPOINTS = Object.freeze({ status: "connection.status", beginProvisioning: "provision.begin", pollProvisioning: "provision.poll", submitVerification: "provision.verify", cancelProvisioning: "provision.cancel", reconnectBot: "bot.reconnect", deleteBot: "bot.delete" }); var ACCOUNT_STATES2 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]); var PROVISION_STATES2 = /* @__PURE__ */ new Set([ "starting", "pending", "scanned", "needs_verification", "connecting", "connected", "expired", "failed", "cancelled" ]); function isRecord3(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function string(value, fallback = "") { return typeof value === "string" && value.trim() ? value.trim() : fallback; } function timestamp2(value) { return typeof value === "number" && Number.isFinite(value) ? value : null; } function unwrapRpcResult3(result) { if (!isRecord3(result) || typeof result.ok !== "boolean") { throw new Error("\u5FAE\u4FE1\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94"); } if (!result.ok) { const error = new Error(string(result.error?.message, "\u5FAE\u4FE1\u64CD\u4F5C\u5931\u8D25")); error.code = string(result.error?.code, "WEIXIN_RPC_ERROR"); throw error; } return result.value; } function safeQrSource3(value) { return typeof value === "string" && /^data:image\/(?:png|webp|svg\+xml)(?:;charset=[^;,]+)?;base64,/i.test(value) ? value : void 0; } function safeVerificationUrl(value) { if (typeof value !== "string") return void 0; try { const url = new URL(value); const host = url.hostname.toLowerCase(); return url.protocol === "https:" && (host === "weixin.qq.com" || host.endsWith(".weixin.qq.com")) ? url.toString() : void 0; } catch { return void 0; } } function normalizeProvisioning3(value) { if (!isRecord3(value) || !string(value.attemptId)) { throw new Error("\u5FAE\u4FE1\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1"); } const status = PROVISION_STATES2.has(value.status) ? value.status : "failed"; const result = { attemptId: string(value.attemptId), status, expiresAt: timestamp2(value.expiresAt) ?? Date.now(), pollIntervalMs: Math.min(5e3, Math.max(500, Number(value.pollIntervalMs) || 1e3)), verificationRequired: value.verificationRequired === true || status === "needs_verification" }; const verificationUrl = safeVerificationUrl(value.verificationUrl); const qrCodeDataUrl = safeQrSource3(value.qrCodeDataUrl); if (verificationUrl) result.verificationUrl = verificationUrl; if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl; if (string(value.botId)) result.botId = string(value.botId); if (value.alreadyConnected === true) result.alreadyConnected = true; if (isRecord3(value.error)) { result.error = { code: string(value.error.code, "WEIXIN_PROVISION_FAILED"), message: string(value.error.message, "\u5FAE\u4FE1\u7ED1\u5B9A\u6CA1\u6709\u5B8C\u6210") }; } return result; } function normalizeBot3(value) { if (!isRecord3(value) || !string(value.botId) || !isRecord3(value.bot)) return null; const state = ACCOUNT_STATES2.has(value.state) ? value.state : "error"; const connected = value.connected === true; return { botId: string(value.botId), state: connected ? "connected" : state, connected, configured: value.configured === true, bot: { name: string(value.bot.name, "\u5FAE\u4FE1\u673A\u5668\u4EBA"), accountIdMasked: string(value.bot.accountIdMasked, "\u5DF2\u5B89\u5168\u4FDD\u5B58") }, health: { status: string(value.health?.status, connected ? "healthy" : "offline"), summary: string(value.health?.summary, connected ? "\u5FAE\u4FE1\u8FDE\u63A5\u6B63\u5E38" : "\u5FAE\u4FE1\u8FDE\u63A5\u672A\u5C31\u7EEA"), lastCheckedAt: timestamp2(value.health?.lastCheckedAt) }, stats: { messagesReceived: Math.max(0, Number(value.stats?.messagesReceived) || 0), messagesReplied: Math.max(0, Number(value.stats?.messagesReplied) || 0) }, error: isRecord3(value.error) ? { code: string(value.error.code, "WEIXIN_ACCOUNT_ERROR"), message: string(value.error.message, "\u5FAE\u4FE1\u8FDE\u63A5\u672A\u5C31\u7EEA") } : null }; } function normalizeSnapshot2(value) { if (!isRecord3(value) || !Array.isArray(value.bots)) { throw new Error("\u5FAE\u4FE1\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u8D26\u53F7\u5217\u8868"); } const bots = value.bots.map(normalizeBot3).filter(Boolean); return { schemaVersion: Number(value.schemaVersion) || 1, revision: Number(value.revision) || 0, state: string(value.state, "offline"), bots, totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length }, provisioning: value.provisioning ? normalizeProvisioning3(value.provisioning) : null }; } function presentError3(error) { return { code: string(error?.code, "WEIXIN_ERROR"), message: string(error?.message, "\u5FAE\u4FE1\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5") }; } function formatRemaining3(milliseconds) { const seconds = Math.max(0, Math.ceil(milliseconds / 1e3)); return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`; } // plugin-src/client/channels/weixin/styles.js var WEIXIN_STYLE_ID = "xmanrui-dsh-weixin-settings"; var CSS3 = String.raw` .dxw-page { --dxw-accent: #07c160; --dxw-accent-dark: #05994c; --dxw-success: var(--dsw-alias-state-success-primary, #20a162); --dxw-warning: var(--dsw-alias-state-warning-primary, #d97706); --dxw-error: var(--dsw-alias-state-error-primary, #d54941); width: 100%; max-width: 880px; display: flex; flex-direction: column; gap: 18px; padding: 2px 0 28px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } .dxw-page *, .dxw-page *::before, .dxw-page *::after { box-sizing: border-box; } .dxw-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } .dxw-heading h2, .dxw-heading p, .dxw-card h3, .dxw-card p { margin: 0; } .dxw-eyebrow { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; font-weight: 650; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 3px; } .dxw-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .dxw-heading p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; } .dxw-tools, .dxw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } .dxw-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .dxw-dot { width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; flex: none; } .dxw-dot[data-tone="success"] { background: var(--dxw-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dxw-success) 14%, transparent); } .dxw-dot[data-tone="warning"] { background: var(--dxw-warning); } .dxw-dot[data-tone="error"] { background: var(--dxw-error); } .dxw-button { min-height: 34px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; padding: 0 13px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; cursor: pointer; text-decoration: none; transition: border-color .15s ease, background .15s ease, transform .15s ease; } .dxw-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dxw-button:active:not(:disabled) { transform: translateY(1px); } .dxw-button:focus-visible, .dxw-input:focus-visible { outline: 2px solid color-mix(in srgb, var(--dxw-accent) 70%, white); outline-offset: 2px; } .dxw-button:disabled { cursor: not-allowed; opacity: .55; } .dxw-button[data-kind="primary"] { color: white; border-color: var(--dxw-accent); background: var(--dxw-accent); } .dxw-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--dxw-accent-dark); background: var(--dxw-accent-dark); } .dxw-button[data-kind="danger"] { color: var(--dxw-error); } .dxw-card { overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } .dxw-cardBody { padding: 24px; } .dxw-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; } .dxw-empty h3 { font-size: 18px; margin-bottom: 8px; } .dxw-empty p { max-width: 560px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .dxw-empty .dxw-actions { margin-top: 20px; } .dxw-logo { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; color: white; background: var(--dxw-accent); box-shadow: 0 18px 45px rgb(7 193 96 / 22%); } .dxw-logo svg { width: 62px; height: 62px; } .dxw-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: center; } .dxw-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; } .dxw-qrFrame { position: relative; width: 270px; aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 16px; background: white; } .dxw-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; } .dxw-qrFallback { padding: 24px; text-align: center; color: #646a73; } .dxw-expired { position: absolute; inset: 0; display: grid; place-items: center; padding: 30px; color: white; text-align: center; font-weight: 650; background: rgb(31 35 41 / 76%); backdrop-filter: blur(3px); } .dxw-countdown { width: 270px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .dxw-countdown div { display: flex; justify-content: space-between; margin-bottom: 6px; } .dxw-progress { height: 4px; overflow: hidden; border-radius: 99px; background: #eef0f3; } .dxw-progress span { display: block; width: var(--dxw-progress); height: 100%; background: var(--dxw-accent); transition: width .2s linear; } .dxw-qrCopy h3 { margin: 9px 0 8px; font-size: 18px; } .dxw-qrCopy > p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .dxw-steps { margin: 18px 0 22px; padding-left: 22px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 1.9; } .dxw-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 600; } .dxw-verify { max-width: 560px; margin: 0 auto; padding: 32px; text-align: center; } .dxw-verify h3 { margin: 8px 0; font-size: 19px; } .dxw-verify p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; } .dxw-codeRow { display: flex; justify-content: center; gap: 10px; margin: 24px 0 10px; } .dxw-input { width: 190px; height: 42px; padding: 0 14px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-body, white); color: inherit; font: inherit; font-size: 18px; letter-spacing: .16em; text-align: center; } .dxw-statusNotice, .dxw-error { display: flex; align-items: center; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dxw-error) 28%, transparent); border-radius: 10px; color: var(--dxw-error); background: color-mix(in srgb, var(--dxw-error) 7%, transparent); font-size: 13px; } .dxw-error { align-items: flex-start; flex-direction: column; padding: 22px; } .dxw-error h3 { font-size: 17px; } .dxw-errorCode { font-family: ui-monospace, SFMono-Regular, monospace; font-size: 11px; opacity: .8; } .dxw-listHeading { display: flex; justify-content: space-between; align-items: center; margin: 2px 0 9px; } .dxw-listHeading h3 { margin: 0; font-size: 14px; } .dxw-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; } .dxw-list { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; } .dxw-accountTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } .dxw-accountIdentity { display: flex; align-items: center; gap: 12px; min-width: 0; } .dxw-avatar { width: 42px; height: 42px; display: grid; place-items: center; flex: none; border-radius: 12px; color: white; background: var(--dxw-accent); } .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(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; } .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .dxw-accountFooter .dxw-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } .dxw-accountFooter .dxw-button { flex: none; white-space: nowrap; } .dxw-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .dxw-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--dxw-error) 25%, transparent); background: color-mix(in srgb, var(--dxw-error) 5%, transparent); } .dxw-confirm strong { display: block; font-size: 14px; margin-bottom: 6px; } .dxw-confirm p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.55; } .dxw-confirm .dxw-actions { margin-top: 13px; } .dxw-loading { padding: 36px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } .dxw-spinner { width: 24px; height: 24px; margin: 0 auto 12px; border: 3px solid #e6e8eb; border-top-color: var(--dxw-accent); border-radius: 50%; animation: dxw-spin .8s linear infinite; } .dxw-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } @keyframes dxw-spin { to { transform: rotate(360deg); } } @media (max-width: 720px) { .dxw-heading, .dxw-accountTop { flex-direction: column; align-items: stretch; } .dxw-empty { grid-template-columns: minmax(0, 1fr); } .dxw-logo { display: none; } .dxw-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } .dxw-qrCopy { width: 100%; } .dxw-metrics { grid-template-columns: minmax(0, 1fr); } .dxw-cardBody { padding: 20px; } } @media (prefers-reduced-motion: reduce) { .dxw-page *, .dxw-page *::before, .dxw-page *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } } `; function installWeixinStyles() { if (typeof document === "undefined") return () => { }; const existing = document.querySelector(`style[data-plugin-css="${WEIXIN_STYLE_ID}"]`); if (existing) return () => { }; const style = document.createElement("style"); style.dataset.plugin = "@xmanrui/dsh-weixin"; style.dataset.pluginCss = WEIXIN_STYLE_ID; style.textContent = CSS3; document.head.appendChild(style); return () => style.remove(); } // plugin-src/client/channels/weixin/index.js var h4 = React5.createElement; var Button5 = React5.forwardRef(function Button6({ children, kind = "secondary", className = "", ...props }, ref) { return h4("button", { ...props, ref, type: "button", className: `dxw-button ${className}`.trim(), "data-kind": kind }, children); }); function Heading3({ totals, adding, busy, onAdd, addButtonRef }) { return h4( "div", { className: "dxw-heading" }, h4( "div", { className: "dxw-tools" }, h4(Button5, { kind: "primary", className: "dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u5FAE\u4FE1"), totals.configured > 0 ? h4( "div", { className: "dxw-badge dim-onlineBadge" }, h4("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) ) : null ) ); } function LoadingView3() { return h4( "div", { className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, h4("div", { className: "dxw-spinner dim-spinner" }), h4("span", null, "\u6B63\u5728\u8BFB\u53D6\u5FAE\u4FE1\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView3({ onStart, busy }) { return h4( "div", { className: "dxw-card dim-surfaceCard" }, h4( "div", { className: "dxw-cardBody dxw-empty dim-surfaceBody dim-emptyView" }, h4( "div", { className: "dim-emptyCopy" }, h4( "div", { className: "dxw-stateLabel dim-stateLabel" }, h4("span", { className: "dxw-dot dim-stateDot" }), h4("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1") ), 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 dim-viewActions" }, h4( Button5, { kind: "primary", onClick: onStart, disabled: busy }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u5FAE\u4FE1\u4E8C\u7EF4\u7801" ) ) ), h4("div", { className: "dxw-logo dim-emptyBrand", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 64 })) ) ); } function QrPanel2({ provision, now, busy, onRefresh, onCancel }) { 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); React5.useEffect(() => setImageFailed(false), [source]); return h4( "div", { className: "dxw-card dim-surfaceCard" }, h4( "div", { className: "dxw-cardBody dxw-qrLayout dim-surfaceBody dim-qrLayout" }, h4( "div", { className: "dxw-qrColumn dim-qrColumn" }, h4( "div", { className: "dxw-qrFrame dim-qrFrame" }, 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) }) : h4("div", { className: "dxw-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"), expired ? h4("div", { className: "dxw-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), h4( "div", { className: "dxw-countdown dim-countdown" }, h4("div", { className: "dim-countdownTop" }, h4("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h4("strong", null, formatRemaining3(remaining))), h4( "div", { className: "dxw-progress dim-progress", "aria-hidden": "true" }, h4("span", { style: { "--dxw-progress": `${progress}%` } }) ) ) ), h4( "div", { className: "dxw-qrCopy dim-qrCopy" }, h4( "div", { className: "dxw-stateLabel dim-stateLabel" }, h4("span", { className: "dxw-dot dim-stateDot", "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") ), 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 dim-steps" }, 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") ), h4( "div", { className: "dxw-actions dim-viewActions" }, 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 ? 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] = React5.useState(""); const valid = /^\d{4,8}$/.test(code); React5.useEffect(() => setCode(""), [provision.attemptId]); return h4( "div", { className: "dxw-card dim-surfaceCard" }, h4( "form", { className: "dxw-verify dim-specialView", onSubmit: (event) => { event.preventDefault(); if (valid && !busy) onSubmit(code); } }, h4( "div", { className: "dxw-stateLabel" }, h4("span", { className: "dxw-dot", "data-tone": "warning" }), h4("span", null, "\u9700\u8981\u914D\u5BF9\u7801") ), 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" }, h4("input", { className: "dxw-input", value: code, inputMode: "numeric", autoComplete: "one-time-code", maxLength: 8, "aria-label": "\u5FAE\u4FE1\u914D\u5BF9\u7801", onChange: (event) => setCode(event.target.value.replace(/\D/g, "").slice(0, 8)), autoFocus: true }), h4("button", { type: "submit", className: "dxw-button", "data-kind": "primary", disabled: !valid || busy }, busy ? "\u6B63\u5728\u9A8C\u8BC1\u2026" : "\u7EE7\u7EED\u8FDE\u63A5") ), h4(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u7ED1\u5B9A") ) ); } function ProgressPanel2({ scanned, onCancel, busy }) { return h4( "div", { className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, h4("div", { className: "dxw-spinner dim-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 dim-viewActions", style: { justifyContent: "center", marginTop: 14 } }, 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 h4( "div", { className: "dxw-card dim-surfaceCard" }, h4( "div", { className: "dxw-error dim-inlineError", role: "alert" }, 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 dim-viewActions" }, h4(Button5, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), h4(Button5, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) ) ); } function checkedTime2(timestamp3) { if (!timestamp3) return "\u5C1A\u672A\u68C0\u67E5"; try { return new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(new Date(timestamp3)); } catch { return "\u521A\u521A"; } } 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 h4( "article", { className: "dxw-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId }, h4( "div", { className: "dxw-cardBody dim-botCardBody" }, h4( "div", { className: "dxw-accountTop dim-botCardTop" }, h4( "div", { className: "dxw-accountIdentity dim-botIdentity" }, h4("div", { className: "dxw-avatar dim-botAvatar", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 27 })), h4("div", { className: "dim-botName" }, h4("h3", null, account.bot.name), h4("p", null, account.bot.accountIdMasked)) ), h4( "div", { className: "dxw-health dim-botHealth" }, h4("span", { className: "dxw-dot dim-healthDot", "data-tone": tone }), h4("span", null, account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA") ) ), h4( "dl", { className: "dxw-metrics dim-botMetrics" }, h4( "div", { className: "dxw-metric dim-botMetric" }, h4("dt", null, "\u6D88\u606F\u901A\u9053"), h4("dd", null, account.connected ? "iLink \u957F\u8F6E\u8BE2" : "\u79BB\u7EBF") ), h4( "div", { className: "dxw-metric dim-botMetric" }, h4("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h4("dd", null, checkedTime2(account.health.lastCheckedAt)) ) ), h4( "div", { className: "dxw-accountFooter dim-cardFooter" }, h4("div", { className: "dxw-summary dim-cardSummary" }, account.error?.message ?? account.health.summary), h4( "div", { className: "dxw-actions dim-cardActions" }, h4( Button5, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), h4(Button5, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") ) ) ), removing ? h4( "div", { className: "dxw-confirm dim-confirm", role: "alertdialog" }, 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 dim-viewActions" }, 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" ) ) ) : null ); } function AccountList2(props) { return h4( "section", { className: "dim-listSection" }, h4("div", { className: "dxw-listHeading dim-listHeading" }, h4("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7"), h4("span", null, `${props.bots.length} \u4E2A`)), h4("ul", { className: "dxw-list dim-botList" }, props.bots.map((account) => h4( "li", { key: account.botId }, h4(AccountCard2, { account, busy: props.busyByBot[account.botId], removing: props.removeTarget === account.botId, onReconnect: () => props.onReconnect(account), onRequestRemove: () => props.onRequestRemove(account), onConfirmRemove: () => props.onConfirmRemove(account), onCancelRemove: props.onCancelRemove }) ))) ); } var EMPTY_TOTALS3 = Object.freeze({ configured: 0, connected: 0 }); function mergeWeixinProvisioningSnapshot(current, incoming, { restoreProvisioning = false } = {}) { if (!incoming || !current && !restoreProvisioning) return current; if (current && current.attemptId !== incoming.attemptId) return current; return { ...current, ...incoming, durationMs: current?.durationMs ?? 5 * 6e4 }; } function WeixinSettingsTab({ rpcCall }) { const [model, setModel] = React5.useState({ phase: "loading", bots: [], totals: EMPTY_TOTALS3, revision: 0, error: 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 = React5.useCallback((value) => { setNotice(""); scheduleAnimationFrame(() => { if (value) setNotice(value); }, "announcement"); }, [scheduleAnimationFrame]); const invoke = React5.useCallback(async (endpoint, payload = {}, signal) => { return unwrapRpcResult3(await rpcCall(endpoint, payload, signal)); }, [rpcCall]); const loadStatus = React5.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => { if (!silent) setModel((current) => ({ ...current, phase: "loading", error: null })); try { const snapshot = normalizeSnapshot2(await invoke(WEIXIN_ENDPOINTS.status, {}, signal)); if (signal?.aborted) return void 0; setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision, error: null }); if (snapshot.provisioning) { setProvision((current) => mergeWeixinProvisioningSnapshot( current, snapshot.provisioning, { restoreProvisioning } )); } return snapshot; } catch (error) { if (signal?.aborted || error?.name === "AbortError") return void 0; setModel((current) => ({ ...current, phase: silent && current.phase === "ready" ? "ready" : "error", error: presentError3(error) })); return void 0; } }, [invoke]); React5.useEffect(() => { const controller = new AbortController(); void loadStatus({ signal: controller.signal, restoreProvisioning: true }); return () => controller.abort(); }, [loadStatus]); React5.useEffect(() => { if (model.phase !== "ready") return void 0; const controller = new AbortController(); let running = false; const timer = window.setInterval(async () => { if (running) return; running = true; await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); running = false; }, 15e3); return () => { controller.abort(); window.clearInterval(timer); }; }, [loadStatus, model.phase]); 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 = React5.useCallback(async ({ replace = false } = {}) => { setBusy(true); try { if (replace && provision?.attemptId) { await invoke(WEIXIN_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId }); } setProvision({ status: "starting" }); const started = normalizeProvisioning3(await invoke(WEIXIN_ENDPOINTS.beginProvisioning, { locale: "zh-CN" })); setNow(Date.now()); setProvision({ ...started, durationMs: Math.max(1, started.expiresAt - Date.now()) }); announce("\u5FAE\u4FE1\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u3002"); } catch (error) { setProvision({ status: "failed", error: presentError3(error), ...provision?.attemptId ? { attemptId: provision.attemptId } : {} }); } finally { setBusy(false); } }, [announce, invoke, provision?.attemptId]); const cancelProvisioning = React5.useCallback(async () => { setBusy(true); try { if (provision?.attemptId && !["failed", "expired", "cancelled"].includes(provision.status)) { await invoke(WEIXIN_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId }); } setProvision(null); announce("\u5DF2\u53D6\u6D88\u5FAE\u4FE1\u7ED1\u5B9A\u3002"); scheduleAnimationFrame(() => addButtonRef.current?.focus(), "focus"); } catch (error) { setProvision((current) => ({ ...current, status: "failed", error: presentError3(error) })); } finally { setBusy(false); } }, [announce, invoke, provision?.attemptId, provision?.status, scheduleAnimationFrame]); const submitVerification = React5.useCallback(async (verifyCode) => { if (!provision?.attemptId) return; setBusy(true); try { const next = normalizeProvisioning3(await invoke(WEIXIN_ENDPOINTS.submitVerification, { attemptId: provision.attemptId, verifyCode })); setProvision((current) => ({ ...current, ...next })); announce("\u914D\u5BF9\u7801\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u7B49\u5F85\u5FAE\u4FE1\u786E\u8BA4\u3002"); } catch (error) { setProvision((current) => ({ ...current, status: "failed", error: presentError3(error) })); } finally { setBusy(false); } }, [announce, invoke, provision?.attemptId]); React5.useEffect(() => { const attemptId = provision?.attemptId; if (!attemptId || !["pending", "scanned", "connecting"].includes(provision.status)) return void 0; const controller = new AbortController(); const scheduler = createPollScheduler({ setTimeoutFn: (callback, delayMs) => window.setTimeout(callback, delayMs), clearTimeoutFn: (timer) => window.clearTimeout(timer) }); const poll = async () => { try { const result = normalizeProvisioning3(await invoke( WEIXIN_ENDPOINTS.pollProvisioning, { attemptId }, controller.signal )); if (scheduler.disposed) return; if (result.status === "connected") { const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false }); if (scheduler.disposed) return; const account = snapshot?.bots.find((bot) => bot.botId === result.botId); if (!account?.connected) { setProvision((current) => current?.attemptId === attemptId ? { ...current, ...result, status: "connecting" } : current); scheduler.schedule(poll, result.pollIntervalMs); return; } setProvision(null); announce(result.alreadyConnected ? "\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\u5DF2\u7ECF\u7ED1\u5B9A\u5E76\u4FDD\u6301\u5728\u7EBF\u3002" : "\u5FAE\u4FE1\u5DF2\u7ED1\u5B9A\uFF0C\u53EF\u4EE5\u5F00\u59CB\u5411\u5DF2\u7ED1\u5B9A\u7684\u673A\u5668\u4EBA\u53D1\u6D88\u606F\u3002"); return; } setProvision((current) => current?.attemptId === attemptId ? { ...current, ...result, durationMs: current.durationMs } : current); if (["pending", "scanned", "connecting"].includes(result.status)) { scheduler.schedule(poll, result.pollIntervalMs); } } catch (error) { if (scheduler.disposed || error?.name === "AbortError") return; setProvision((current) => current?.attemptId === attemptId ? { ...current, status: "failed", error: presentError3(error) } : current); } }; scheduler.schedule(poll, provision.pollIntervalMs ?? 1e3); return () => { scheduler.dispose(); controller.abort(); }; }, [announce, invoke, loadStatus, provision?.attemptId, provision?.status, provision?.pollIntervalMs]); const setBotBusy = React5.useCallback((botId, value) => { setBusyByBot((current) => { const next = { ...current }; if (value) next[botId] = value; else delete next[botId]; return next; }); }, []); const reconnect = React5.useCallback(async (account) => { setBotBusy(account.botId, "reconnect"); try { const snapshot = normalizeSnapshot2(await invoke(WEIXIN_ENDPOINTS.reconnectBot, { botId: account.botId })); setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision })); const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId); announce(refreshed?.connected ? "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002" : "\u5FAE\u4FE1\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002"); } catch (error) { announce(`\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF1A${presentError3(error).message}`); } finally { setBotBusy(account.botId, null); } }, [announce, invoke, setBotBusy]); const remove = React5.useCallback(async (account) => { setBotBusy(account.botId, "delete"); try { const snapshot = normalizeSnapshot2(await invoke(WEIXIN_ENDPOINTS.deleteBot, { botId: account.botId, confirm: true })); setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision })); setRemoveTarget(null); announce("\u5FAE\u4FE1\u8D26\u53F7\u53CA\u672C\u673A\u51ED\u636E\u5DF2\u79FB\u9664\u3002"); } catch (error) { announce(`\u79FB\u9664\u5931\u8D25\uFF1A${presentError3(error).message}`); } finally { setBotBusy(account.botId, null); } }, [announce, invoke, setBotBusy]); let provisionView = null; if (provision?.status === "starting") { provisionView = h4(ProgressPanel2, { busy }); } else if (["pending", "scanned"].includes(provision?.status)) { provisionView = h4(QrPanel2, { provision, now, busy, onRefresh: () => void startProvisioning({ replace: true }), onCancel: () => void cancelProvisioning() }); } else if (provision?.status === "needs_verification") { provisionView = h4(VerificationPanel, { provision, busy, onSubmit: (code) => void submitVerification(code), onCancel: () => void cancelProvisioning() }); } else if (provision?.status === "connecting") { provisionView = h4(ProgressPanel2, { scanned: true, busy, onCancel: () => void cancelProvisioning() }); } else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) { provisionView = h4(ProvisionError3, { provision, busy, onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }), onClose: () => void cancelProvisioning() }); } return h4( "section", { className: "dxw-page dim-channelPage", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" }, h4(Heading3, { totals: model.totals, adding: Boolean(provision), busy, onAdd: () => void startProvisioning(), addButtonRef }), h4("div", { className: "dxw-visuallyHidden", role: "status", "aria-live": "polite" }, notice), model.error && model.phase === "ready" ? h4("div", { className: "dxw-statusNotice dim-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 dim-surfaceCard" }, h4( "div", { className: "dxw-error dim-inlineError" }, 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") ) ) : h4( React5.Fragment, null, provisionView, model.bots.length === 0 && !provision ? h4(EmptyView3, { onStart: () => void startProvisioning(), busy }) : null, model.bots.length > 0 ? h4(AccountList2, { bots: model.bots, busyByBot, removeTarget, onReconnect: (account) => void reconnect(account), onRequestRemove: (account) => setRemoveTarget(account.botId), onConfirmRemove: (account) => void remove(account), onCancelRemove: () => setRemoveTarget(null) }) : null ) ); } // plugin-src/client/styles.js var IM_STYLE_ID = "xmanrui-dsh-im-settings"; var CSS4 = String.raw` .dim-page { --dim-blue: #3370ff; --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent); width: 100%; max-width: 1080px; padding: 2px 0 30px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } .dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; } .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-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; } .dim-rail::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-line-border, #dfe1e5); } .dim-channel { width: 100%; min-height: 48px; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--dsw-alias-line-border, #eef0f3); border-radius: 14px; color: inherit; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 2px 8px rgb(31 35 41 / 3%); font: inherit; text-align: left; cursor: pointer; transition: border-color .16s ease, background .16s ease, box-shadow .16s ease; } .dim-channel:hover { border-color: color-mix(in srgb, var(--dim-blue) 25%, var(--dsw-alias-line-border, #eef0f3)); background: color-mix(in srgb, var(--dim-blue) 2%, var(--dsw-alias-bg-layer-3, #fff)); box-shadow: 0 5px 16px rgb(31 35 41 / 5%); } .dim-channel[aria-selected="true"] { border-color: color-mix(in srgb, var(--dim-blue) 43%, white); color: var(--dim-blue); background: color-mix(in srgb, var(--dim-blue) 9%, white); box-shadow: 0 3px 12px rgb(51 112 255 / 7%); } .dim-channel:focus-visible { outline: none; border-color: color-mix(in srgb, var(--dim-blue) 72%, white); box-shadow: 0 0 0 1px color-mix(in srgb, var(--dim-blue) 24%, transparent) inset, 0 3px 12px rgb(51 112 255 / 7%); } .dim-logo { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 9px; box-shadow: 0 1px 3px rgb(31 35 41 / 7%); } .dim-logo svg { display: block; width: 20px; height: 20px; } .dim-logoWeixin { color: white; background: #07c160; } .dim-logoWeixin svg { width: 19px; height: 19px; } .dim-logoFeishu { background: white; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); } .dim-logoFeishu svg { width: 22px; height: 22px; } .dim-logoDingtalk { color: white; background: #1677ff; } .dim-logoDingtalk svg { width: 24px; height: 24px; } .dim-channelCopy { min-width: 0; display: block; } .dim-channelCopy strong { overflow: hidden; color: inherit; font-size: 14px; line-height: 20px; font-weight: 680; text-overflow: ellipsis; white-space: nowrap; } .dim-divider { width: 1px; min-height: 520px; background: var(--dsw-alias-line-divider, #eef0f3); } .dim-panel { min-width: 0; container-type: inline-size; } .dim-panel .bxf-page, .dim-panel .dxw-page, .dim-panel .ddt-page { width: 100%; max-width: none; padding: 0 0 24px; } .dim-panel .bxf-heading, .dim-panel .dxw-heading, .dim-panel .ddt-heading { justify-content: flex-end; } .dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { width: 100%; justify-content: space-between; } .dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid #1677ff; border-radius: 8px; color: #fff; background: #1677ff; box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; white-space: nowrap; } .dim-panel .bxf-headingTools .dim-scanButton:hover:not(:disabled), .dim-panel .dxw-tools .dim-scanButton:hover:not(:disabled), .dim-panel .ddt-tools .dim-scanButton:hover:not(:disabled) { border-color: #0958d9; background: #0958d9; } .dim-panel .bxf-headingTools .dim-onlineBadge, .dim-panel .dxw-tools .dim-onlineBadge, .dim-panel .ddt-tools .dim-onlineBadge { flex: none; min-height: 30px; display: inline-flex; align-items: center; gap: 0; padding: 0 11px; border: 0; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } .dim-panel .dim-channelPage { width: 100%; max-width: none; display: flex; flex-direction: column; gap: 18px; padding: 0 0 24px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } .dim-panel .dim-surfaceCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } .dim-panel .dim-surfaceCard::before { display: none; } .dim-panel .dim-surfaceBody { padding: 24px; } .dim-panel .dim-listSection { display: flex; flex-direction: column; gap: 0; } .dim-panel .dim-listHeading { min-height: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 2px 0 9px; padding: 0; } .dim-panel .dim-listHeading h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: normal; font-weight: 650; } .dim-panel .dim-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: normal; } .dim-panel .dim-botList { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; } .dim-panel .dim-loadingView { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } .dim-panel .dim-loadingView h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; line-height: normal; font-weight: 650; } .dim-panel .dim-loadingView p { margin: 0; line-height: 1.6; } .dim-panel .dim-spinner { width: 24px; height: 24px; margin: 0 auto 13px; border: 3px solid var(--dsw-alias-line-border, #e6e8eb); border-top-color: #1677ff; border-radius: 50%; animation: dim-spin .8s linear infinite; } @keyframes dim-spin { to { transform: rotate(360deg); } } .dim-panel .dim-emptyView { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; } .dim-panel .dim-emptyCopy { min-width: 0; } .dim-panel .dim-emptyCopy h3 { margin: 8px 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } .dim-panel .dim-emptyCopy > p { max-width: 560px; margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .dim-panel .dim-emptyBrand { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; box-shadow: 0 18px 45px rgb(22 119 255 / 18%); } .dim-panel .dim-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; font-weight: 600; } .dim-panel .dim-stateDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #8f959e); box-shadow: none; } .dim-panel .dim-stateDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); } .dim-panel .dim-stateDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } .dim-panel .dim-stateDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } .dim-panel .dim-viewActions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 20px; } .dim-panel .dim-viewActions .bxf-button, .dim-panel .dim-viewActions .dxw-button, .dim-panel .dim-viewActions .ddt-button { min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } .dim-panel .dim-viewActions .bxf-button[data-kind="primary"], .dim-panel .dim-viewActions .dxw-button[data-kind="primary"], .dim-panel .dim-viewActions .ddt-button[data-kind="primary"] { border-color: #1677ff; color: #fff; background: #1677ff; box-shadow: none; } .dim-panel .dim-viewActions .bxf-button[data-kind="danger"], .dim-panel .dim-viewActions .dxw-button[data-kind="danger"], .dim-panel .dim-viewActions .ddt-button[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } .dim-panel .dim-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; } .dim-panel .dim-qrColumn { width: 100%; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 12px; } .dim-panel .dim-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 16px; background: #fff; } .dim-panel .dim-qrFrame::before { content: ""; position: absolute; inset: 7px; z-index: 0; border: 1px solid color-mix(in srgb, #1677ff 16%, var(--dsw-alias-line-border, #dfe1e5)); border-radius: 12px; pointer-events: none; } .dim-panel .dim-qrFrame::after { display: none; } .dim-panel .dim-qrFrame img { position: relative; z-index: 1; width: 100%; height: 100%; display: block; object-fit: contain; } .dim-panel .dim-qrFallback { position: relative; z-index: 1; display: grid; place-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 1.5; text-align: center; } .dim-panel .dim-qrExpired { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding: 20px; color: var(--dsw-alias-label-primary, #1f2329); background: rgb(255 255 255 / 92%); font-size: 15px; line-height: 1.6; font-weight: 650; text-align: center; white-space: pre-line; backdrop-filter: blur(3px); } .dim-panel .dim-countdown { width: min(270px, 100%); margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; } .dim-panel .dim-countdownTop { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; } .dim-panel .dim-countdownTop strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 650; } .dim-panel .dim-progress { height: 4px; overflow: hidden; margin: 0; border-radius: 99px; background: var(--dsw-alias-fill-secondary, #eef0f3); } .dim-panel .dim-progress span { display: block; width: var(--bxf-progress, var(--dxw-progress, var(--ddt-progress, 0%))); height: 100%; border-radius: inherit; background: #1677ff; transition: width .25s linear; } .dim-panel .dim-qrCopy { min-width: 0; overflow-wrap: anywhere; } .dim-panel .dim-qrCopy h3 { margin: 9px 0 8px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } .dim-panel .dim-qrCopy > p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } .dim-panel .dim-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: dim-step; } .dim-panel .dim-steps li { position: relative; min-height: 28px; display: flex; align-items: center; padding: 5px 0 5px 36px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.5; counter-increment: dim-step; } .dim-panel .dim-steps li::before { content: counter(dim-step); position: absolute; left: 0; top: 4px; width: 25px; height: 25px; display: grid; place-items: center; border-radius: 8px; color: #1677ff; background: color-mix(in srgb, #1677ff 10%, white); font-size: 12px; font-weight: 650; } .dim-panel .dim-specialView { padding: 32px; text-align: center; } .dim-panel .dim-statusNotice { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 22%, white); border-radius: 10px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); font-size: 13px; line-height: 1.5; } .dim-panel .dim-inlineError { display: flex; align-items: flex-start; flex-direction: column; gap: 10px; padding: 22px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); } .dim-panel .dim-inlineError > div { min-width: 0; } .dim-panel .dim-inlineError h3 { margin: 0; color: inherit; font-size: 17px; line-height: 1.35; font-weight: 650; } .dim-panel .dim-inlineError p { margin: 7px 0 0; color: inherit; line-height: 1.6; } .dim-panel .dim-confirm { padding: 18px 24px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dim-panel .dim-confirm strong, .dim-panel .dim-confirm h4 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 1.4; font-weight: 650; } .dim-panel .dim-confirm p { margin: 7px 0 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; } .dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; } .dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin-top: 0; } .dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } .dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dim-panel .dim-cardActions .dim-cardAction[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } .dim-panel .dim-botCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } .dim-panel .dim-botCard::before { display: none; } .dim-panel .dim-botCardBody { position: relative; padding: 24px; } .dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } .dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap: 12px; } .dim-panel .dim-botAvatar { flex: none; width: 42px; height: 42px; display: grid; place-items: center; overflow: hidden; border-radius: 12px; box-shadow: none; } .dim-panel .dim-botAvatar svg { width: 29px; height: 29px; } .dim-panel .dim-botName { min-width: 0; } .dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } .dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } .dim-panel .dim-botCard .dim-botHealth { flex: none; min-height: 0; display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0; border-radius: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } .dim-panel .dim-botCard .dim-healthDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; box-shadow: none; } .dim-panel .dim-botCard .dim-healthDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary, #20a162) 14%, transparent); } .dim-panel .dim-botCard .dim-healthDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } .dim-panel .dim-botCard .dim-healthDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } .dim-panel .dim-botMetrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } .dim-panel .dim-botMetric { min-width: 0; padding: 12px; border: 0; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } .dim-panel .dim-botMetric dt { margin: 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; font-weight: 400; line-height: normal; } .dim-panel .dim-botMetric dd { overflow: hidden; margin: 5px 0 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; font-weight: 400; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } .dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; } .dim-panel .ddt-headingCopy { display: none; } .dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); } @container (max-width: 680px) { .dim-panel .dim-emptyView { min-height: 0; grid-template-columns: minmax(0, 1fr); } .dim-panel .dim-emptyBrand { display: none; } .dim-panel .dim-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } .dim-panel .dim-qrColumn { width: 100%; min-width: 0; } .dim-panel .dim-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } .dim-panel .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } .dim-panel .ddt-qrColumn { width: 100%; min-width: 0; } .dim-panel .ddt-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } } @media (max-width: 840px) { .dim-title { align-items: flex-start; } .dim-layout { grid-template-columns: minmax(0, 1fr); gap: 18px; } .dim-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); } .dim-divider { display: none; } .dim-rail { max-height: none; overflow: visible; padding-right: 1px; } .dim-channel { min-height: 48px; } } @media (max-width: 720px) { .dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; } } @media (max-width: 560px) { .dim-title { flex-direction: column; gap: 10px; } .dim-title p { white-space: normal; } .dim-rail { grid-template-columns: minmax(0, 1fr); } } @media (prefers-reduced-motion: reduce) { .dim-page * { transition-duration: .01ms !important; } } `; function installImStyles() { if (typeof document === "undefined") return () => { }; const existing = document.querySelector(`style[data-plugin-css="${IM_STYLE_ID}"]`); if (existing) return () => { }; const style = document.createElement("style"); style.dataset.plugin = "@xmanrui/dsh-im"; style.dataset.pluginCss = IM_STYLE_ID; style.textContent = CSS4; document.head.appendChild(style); return () => style.remove(); } // plugin-src/client/index.js var h5 = React6.createElement; var name = "im-settings"; var inject = ["slots", "connection"]; var CHANNELS = Object.freeze([ { id: "weixin", label: "\u5FAE\u4FE1" }, { id: "feishu", label: "\u98DE\u4E66" }, { id: "dingtalk", label: "\u9489\u9489" } ]); function WeixinLogo() { return h5( "span", { className: "dim-logo dim-logoWeixin", "aria-hidden": "true" }, h5(WeixinLogoGlyph) ); } function FeishuLogo() { return h5( "span", { className: "dim-logo dim-logoFeishu", "aria-hidden": "true" }, h5(FeishuLogoGlyph) ); } function DingtalkLogo() { return h5( "span", { className: "dim-logo dim-logoDingtalk", "aria-hidden": "true" }, h5(DingtalkLogoGlyph) ); } function ChannelLogo({ channel }) { if (channel === "weixin") return h5(WeixinLogo); if (channel === "feishu") return h5(FeishuLogo); return h5(DingtalkLogo); } function IMSettingsTab({ dingtalkRpcCall, feishuRpcCall, weixinRpcCall }) { const [selected, setSelected] = React6.useState("weixin"); const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0]; return h5( "section", { className: "dim-page", "aria-label": "IM\u673A\u5668\u4EBA\u8BBE\u7F6E" }, h5( "header", { className: "dim-title" }, h5("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness") ), h5( "div", { className: "dim-layout" }, h5( "nav", { className: "dim-rail", role: "tablist", "aria-label": "IM \u6E20\u9053" }, CHANNELS.map((channel) => h5( "button", { key: channel.id, type: "button", role: "tab", id: `dim-tab-${channel.id}`, className: "dim-channel", "aria-selected": channel.id === active.id, "aria-controls": `dim-panel-${channel.id}`, onClick: () => setSelected(channel.id) }, h5(ChannelLogo, { channel: channel.id }), h5( "span", { className: "dim-channelCopy" }, h5("strong", null, channel.label) ) )) ), 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" ? h5(WeixinSettingsTab, { rpcCall: weixinRpcCall }) : active.id === "feishu" ? h5(FeishuSettingsTab, { rpcCall: feishuRpcCall }) : h5(DingtalkSettingsTab, { rpcCall: dingtalkRpcCall })) ) ); } function apply(ctx) { ctx.effect(() => { const disposers = [installFeishuStyles(), installWeixinStyles(), installImStyles()]; return () => { for (const dispose of disposers.reverse()) dispose(); }; }, "im-settings: install combined channel styles"); const feishuRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(FEISHU_RPC_CHANNEL, endpoint, payload, signal); const weixinRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(WEIXIN_RPC_CHANNEL, endpoint, payload, signal); const dingtalkRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(DINGTALK_RPC_CHANNEL, endpoint, payload, signal); ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({ name: "settings.plugins.tab", id: "im", order: 20, label: "IM\u673A\u5668\u4EBA", inject: () => ({ dingtalkRpcCall, feishuRpcCall, weixinRpcCall }) }, IMSettingsTab)); } return module.exports; } });