dsh-im-ops/lib/client.js
2026-08-16 23:08:52 +08:00

6915 lines
303 KiB
JavaScript

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 React11 = __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"
}));
}
function QqLogoGlyph({ size } = {}) {
return h("svg", {
...dimensions(size),
viewBox: "0 0 24 24",
focusable: "false",
"aria-hidden": "true",
"data-im-channel-logo": "qq"
}, h("path", {
fill: "currentColor",
d: "M21.395 15.035a40 40 0 0 0-.803-2.264l-1.079-2.695c.001-.032.014-.562.014-.836C19.526 4.632 17.351 0 12 0S4.474 4.632 4.474 9.241c0 .274.013.804.014.836l-1.08 2.695a39 39 0 0 0-.802 2.264c-1.021 3.283-.69 4.643-.438 4.673.54.065 2.103-2.472 2.103-2.472 0 1.469.756 3.387 2.394 4.771-.612.188-1.363.479-1.845.835-.434.32-.379.646-.301.778.343.578 5.883.369 7.482.189 1.6.18 7.14.389 7.483-.189.078-.132.132-.458-.301-.778-.483-.356-1.233-.646-1.846-.836 1.637-1.384 2.393-3.302 2.393-4.771 0 0 1.563 2.537 2.103 2.472.251-.03.581-1.39-.438-4.673"
}));
}
function WecomLogoGlyph({ size } = {}) {
return h(
"svg",
{
...dimensions(size),
viewBox: "0 0 24 24",
focusable: "false",
"aria-hidden": "true",
"data-im-channel-logo": "wecom"
},
h("path", {
fill: "none",
stroke: "#3370FF",
strokeWidth: "2.35",
strokeLinecap: "round",
strokeLinejoin: "round",
d: "M17.7 14.5c1.05-1.12 1.65-2.52 1.65-4.03 0-3.82-3.58-6.92-8-6.92s-8 3.1-8 6.92 3.58 6.92 8 6.92c1.17 0 2.28-.22 3.28-.62"
}),
h("path", { fill: "#07C160", d: "M16.1 15.15c.7-.7 1.83-.7 2.53 0s.7 1.83 0 2.53-1.83.7-2.53 0-.7-1.83 0-2.53Z" }),
h("path", { fill: "#FFB800", d: "M19.25 13.45a1.36 1.36 0 1 1 1.92 1.92 1.36 1.36 0 0 1-1.92-1.92Z" }),
h("path", { fill: "#FF7A00", d: "M19.55 18.05a1.16 1.16 0 1 1 1.64 1.64 1.16 1.16 0 0 1-1.64-1.64Z" }),
h("path", { fill: "#3370FF", d: "M15.25 18.75a.92.92 0 1 1 1.3 1.3.92.92 0 0 1-1.3-1.3Z" })
);
}
function TelegramLogoGlyph({ size } = {}) {
return h("svg", {
...dimensions(size),
viewBox: "0 0 24 24",
focusable: "false",
"aria-hidden": "true",
"data-im-channel-logo": "telegram"
}, h("path", {
fill: "currentColor",
d: "M23.95 4.57c-.36-1.45-1.43-1.76-2.82-1.24L1.5 10.9c-1.34.52-1.32 1.27-.24 1.6l5.03 1.57 11.66-7.36c.55-.34 1.05-.16.64.21l-9.44 8.52-.37 5.12c.54 0 .78-.24 1.08-.53l2.59-2.51 5.38 3.97c.99.55 1.7.27 1.95-.92L23.95 4.57Z"
}));
}
function DiscordLogoGlyph({ size } = {}) {
return h("svg", {
...dimensions(size),
viewBox: "0 0 24 24",
focusable: "false",
"aria-hidden": "true",
"data-im-channel-logo": "discord"
}, h("path", {
fill: "currentColor",
d: "M20.32 4.37a19.8 19.8 0 0 0-4.89-1.51c-.21.38-.46.89-.63 1.29a18.4 18.4 0 0 0-5.59 0 13 13 0 0 0-.64-1.29c-1.71.29-3.36.8-4.89 1.52C.59 9.09-.25 13.68.17 18.2a19.9 19.9 0 0 0 6 3.04c.48-.66.91-1.36 1.28-2.1-.7-.26-1.37-.58-2-.96.17-.12.33-.25.49-.38 3.86 1.79 8.04 1.79 11.86 0 .16.13.32.26.49.38-.64.38-1.31.7-2.01.97.37.73.8 1.44 1.28 2.09a19.8 19.8 0 0 0 6-3.04c.49-5.24-.84-9.79-3.24-13.83ZM8.02 15.42c-1.16 0-2.11-1.07-2.11-2.38s.93-2.38 2.11-2.38c1.18 0 2.13 1.08 2.11 2.38 0 1.31-.93 2.38-2.11 2.38Zm7.95 0c-1.16 0-2.11-1.07-2.11-2.38s.93-2.38 2.11-2.38c1.18 0 2.13 1.08 2.11 2.38 0 1.31-.93 2.38-2.11 2.38Z"
}));
}
function WhatsappLogoGlyph({ size } = {}) {
return h("svg", {
...dimensions(size),
viewBox: "0 0 24 24",
focusable: "false",
"aria-hidden": "true",
"data-im-channel-logo": "whatsapp"
}, h("path", {
fill: "currentColor",
d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.521.149-.173.198-.297.298-.495.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 0 0-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479s1.065 2.875 1.213 3.074c.149.198 2.095 3.2 5.077 4.487.709.306 1.262.489 1.694.626.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.29.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.981.999-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.83 9.83 0 0 1 2.893 6.991c-.003 5.45-4.437 9.884-9.886 9.884m8.413-18.297A11.8 11.8 0 0 0 12.055 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.9 11.9 0 0 0 5.688 1.448h.005c6.557 0 11.892-5.335 11.895-11.893a11.82 11.82 0 0 0-3.486-8.413"
}));
}
// 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",
bindCredentials: "bot.bind-credentials",
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 id5 = optionalString(value, 128);
return id5 && /^[a-z\d_-]+$/i.test(id5) ? id5 : 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 React3 = __toESM(require("react"), 1);
// plugin-src/client/credential-binding.js
var React2 = __toESM(require("react"), 1);
var h2 = React2.createElement;
function ActionIcon({ children }) {
return h2("svg", {
className: "dim-actionIcon",
width: 15,
height: 15,
viewBox: "0 0 20 20",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
focusable: "false"
}, children);
}
function QrActionIcon() {
return h2(
ActionIcon,
null,
h2("path", {
d: "M2.5 2.5h5v5h-5v-5Zm10 0h5v5h-5v-5Zm-10 10h5v5h-5v-5Z",
stroke: "currentColor",
strokeWidth: "1.6",
strokeLinejoin: "round"
}),
h2("path", {
d: "M11.5 11.5h2v2h-2v-2Zm4 0h2v3h-2v-3Zm-4 4h3v2h-3v-2Zm5 1h1v1h-1v-1Z",
fill: "currentColor"
})
);
}
function CredentialActionIcon() {
return h2(
ActionIcon,
null,
h2("circle", {
cx: "6.25",
cy: "10",
r: "3.5",
stroke: "currentColor",
strokeWidth: "1.6"
}),
h2("path", {
d: "M9.75 10h7.75m-2.5 0v2m-2.5-2v2",
stroke: "currentColor",
strokeWidth: "1.6",
strokeLinecap: "round",
strokeLinejoin: "round"
})
);
}
function CredentialBindingPanel({
channel: channel3,
identityLabel,
identityPlaceholder,
secretLabel,
secretPlaceholder,
busy = false,
error = null,
onSubmit,
onCancel
}) {
const [identity, setIdentity] = React2.useState("");
const [secret, setSecret] = React2.useState("");
const headingId = React2.useId();
const hasIdentity = Boolean(identityLabel);
const submit = (event) => {
event.preventDefault();
const normalizedIdentity = identity.trim();
const normalizedSecret = secret.trim();
if (hasIdentity && !normalizedIdentity || !normalizedSecret || busy) return;
void onSubmit?.({ identity: normalizedIdentity, secret: normalizedSecret });
};
return h2(
"section",
{
className: "ddt-card dim-surfaceCard dim-credentialPanel",
"aria-labelledby": headingId
},
h2("h3", { id: headingId, className: "dim-credentialTitle" }, `\u624B\u52A8\u63A5\u5165${channel3}\u673A\u5668\u4EBA`),
h2(
"form",
{
className: `dim-credentialForm${hasIdentity ? "" : " dim-credentialFormSingle"}`,
onSubmit: submit
},
hasIdentity ? h2(
"label",
{ className: "dim-credentialField" },
h2("span", null, identityLabel),
h2("input", {
value: identity,
onChange: (event) => setIdentity(event.target.value),
placeholder: identityPlaceholder,
maxLength: 512,
autoCapitalize: "none",
autoCorrect: "off",
spellCheck: false,
autoComplete: "off",
disabled: busy,
required: true
})
) : null,
h2(
"label",
{ className: "dim-credentialField" },
h2("span", null, secretLabel),
h2("input", {
type: "password",
value: secret,
onChange: (event) => setSecret(event.target.value),
placeholder: secretPlaceholder,
maxLength: 1024,
autoCapitalize: "none",
autoCorrect: "off",
spellCheck: false,
autoComplete: "new-password",
disabled: busy,
required: true
})
),
error ? h2("p", { className: "dim-credentialError", role: "alert" }, error.message ?? String(error)) : null,
h2(
"div",
{ className: "ddt-actions dim-viewActions dim-credentialActions" },
h2("button", {
type: "submit",
className: "ddt-button",
"data-kind": "primary",
disabled: busy || hasIdentity && !identity.trim() || !secret.trim()
}, busy ? "\u6B63\u5728\u7ED1\u5B9A\u2026" : "\u7ED1\u5B9A\u5E76\u8FDE\u63A5"),
h2("button", {
type: "button",
className: "ddt-button",
onClick: onCancel,
disabled: busy
}, "\u53D6\u6D88")
)
)
);
}
// 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-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 h3 = React3.createElement;
var ACTIVE_PROVISION_STATES = /* @__PURE__ */ new Set(["pending", "scanned", "authorizing", "creating", "connecting"]);
function DingtalkIcon({ size = 28 }) {
return h3("svg", {
width: size,
height: size,
viewBox: "0 0 48 48",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
focusable: "false"
}, h3("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 = React3.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) {
return h3("button", {
...props,
ref,
type: "button",
className: `ddt-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function Heading({ totals, adding, busy, onAdd, onCredential, credentialOpen, addButtonRef }) {
return h3(
"div",
{ className: "ddt-heading" },
h3(
"div",
{ className: "ddt-headingCopy" },
h3("div", { className: "ddt-eyebrow" }, "Channel"),
h3("h2", null, "\u9489\u9489\u673A\u5668\u4EBA"),
h3("p", null, "\u901A\u8FC7\u626B\u7801\u628A\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness")
),
h3(
"div",
{ className: "ddt-tools" },
h3(
"div",
{ className: "dim-bindActions" },
h3(Button, {
kind: "primary",
className: "dim-scanButton",
onClick: onAdd,
disabled: adding || busy,
ref: addButtonRef,
"aria-label": "\u626B\u7801\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA"
}, h3(QrActionIcon), adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA"),
h3(Button, {
kind: "credential",
className: "dim-credentialButton",
onClick: onCredential,
disabled: adding || busy,
"aria-pressed": credentialOpen,
"aria-label": "\u4F7F\u7528 Client ID \u548C Client Secret \u7ED1\u5B9A\u9489\u9489\u673A\u5668\u4EBA"
}, h3(CredentialActionIcon), credentialOpen ? "\u6536\u8D77\u51ED\u636E" : "\u624B\u52A8\u63A5\u5165")
),
totals.configured > 0 ? h3(
"div",
{ className: "ddt-badge dim-onlineBadge" },
h3("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)
) : null
)
);
}
function LoadingView() {
return h3(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h3("div", { className: "ddt-spinner dim-spinner" }),
h3("span", null, "\u6B63\u5728\u8BFB\u53D6\u9489\u9489\u8FDE\u63A5\u72B6\u6001\u2026")
);
}
function EmptyView({ busy, onStart }) {
return h3(
"div",
{ className: "ddt-card dim-surfaceCard" },
h3(
"div",
{ className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" },
h3(
"div",
{ className: "dim-emptyCopy" },
h3(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h3("span", { className: "ddt-dot dim-stateDot" }),
h3("span", null, "\u5C1A\u672A\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA")
),
h3("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u81EA\u52A8\u521B\u5EFA\u5E76\u8FDE\u63A5\u673A\u5668\u4EBA"),
h3("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"),
h3(
"div",
{ className: "ddt-actions dim-viewActions" },
h3(
Button,
{ kind: "primary", onClick: onStart, disabled: busy },
busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u9489\u9489\u4E8C\u7EF4\u7801"
)
)
),
h3(
"div",
{ className: "ddt-brandMark dim-emptyBrand", "aria-hidden": "true" },
h3(DingtalkIcon, { size: 68 })
)
)
);
}
function QrPanel({ provision, now, busy, onRefresh, onCancel }) {
const [imageFailed, setImageFailed] = React3.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);
React3.useEffect(() => setImageFailed(false), [source]);
return h3(
"div",
{ className: "ddt-card dim-surfaceCard" },
h3(
"div",
{ className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" },
h3(
"div",
{ className: "ddt-qrColumn dim-qrColumn" },
h3(
"div",
{ className: "ddt-qrFrame dim-qrFrame" },
source && !imageFailed ? h3("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)
}) : h3("div", { className: "ddt-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"),
expired ? h3("div", { className: "ddt-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null
),
h3(
"div",
{ className: "ddt-countdown dim-countdown" },
h3(
"div",
{ className: "ddt-countdownTop dim-countdownTop" },
h3("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"),
h3("strong", null, formatRemaining(remaining))
),
h3(
"div",
{ className: "ddt-progress dim-progress", "aria-hidden": "true" },
h3("span", { style: { "--ddt-progress": `${progress}%` } })
)
)
),
h3(
"div",
{ className: "ddt-qrCopy dim-qrCopy" },
h3(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h3("span", { className: "ddt-dot dim-stateDot", "data-tone": expired ? "error" : "warning" }),
h3("span", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u7B49\u5F85\u9489\u9489\u626B\u7801\u6388\u6743")
),
h3("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"),
h3("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"),
h3(
"ol",
{ className: "ddt-steps dim-steps" },
h3("li", null, "\u4F7F\u7528\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u7684\u9489\u9489\u8D26\u53F7\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"),
h3("li", null, "\u5728\u6388\u6743\u9875\u70B9\u51FB\u201C\u4E00\u952E\u521B\u5EFA\u65B0\u673A\u5668\u4EBA\u201D"),
h3("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u673A\u5668\u4EBA\u81EA\u52A8\u8FDE\u63A5")
),
h3(
"div",
{ className: "ddt-actions dim-viewActions" },
expired ? h3(Button, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null,
!expired ? h3(Button, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
h3(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88")
)
)
)
);
}
function ProgressPanel({ status, busy, onCancel }) {
const connecting = status === "connecting";
const creating = status === "creating";
return h3(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h3("div", { className: "ddt-spinner dim-spinner" }),
h3("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"),
h3("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"),
h3(
"div",
{ className: "ddt-actions dim-viewActions", style: { justifyContent: "center", marginTop: 14 } },
h3(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 h3(
"div",
{ className: "ddt-card dim-surfaceCard" },
h3(
"div",
{ className: "ddt-inlineError dim-inlineError", role: "alert" },
h3("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"),
h3("p", null, error.message),
h3("span", { className: "ddt-errorCode" }, error.code),
h3(
"div",
{ className: "ddt-actions dim-viewActions" },
h3(Button, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h3(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 = React3.useRef(null);
React3.useEffect(() => cancelRef.current?.focus(), []);
return h3(
"div",
{
className: "ddt-confirm dim-confirm",
role: "alertdialog",
"aria-label": `\u79FB\u9664${account.bot.name}`,
onKeyDown: (event) => {
if (event.key === "Escape" && !busy) onCancel();
}
},
h3("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`),
h3("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"),
h3(
"div",
{ className: "ddt-actions dim-viewActions" },
h3(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h3(
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";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
return h3(
"article",
{ className: "ddt-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId },
h3(
"div",
{ className: "ddt-cardBody dim-botCardBody" },
h3(
"div",
{ className: "ddt-accountTop dim-botCardTop" },
h3(
"div",
{ className: "ddt-accountIdentity dim-botIdentity" },
h3("div", { className: "ddt-avatar dim-botAvatar", "aria-hidden": "true" }, h3(DingtalkIcon, { size: 29 })),
h3(
"div",
{ className: "dim-botName" },
h3("h3", { title: account.bot.name }, account.bot.name),
h3("p", { title: account.bot.clientIdMasked }, account.bot.clientIdMasked)
)
),
h3(
"div",
{ className: "ddt-health dim-botHealth" },
h3("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }),
h3("span", null, stateLabel)
)
),
h3(
"dl",
{ className: "ddt-metrics dim-botMetrics" },
h3(
"div",
{ className: "ddt-metric dim-botMetric" },
h3("dt", null, "\u6D88\u606F\u901A\u9053"),
h3("dd", null, account.connected ? "Stream \u957F\u8FDE\u63A5" : "\u79BB\u7EBF")
),
h3(
"div",
{ className: "ddt-metric dim-botMetric" },
h3("dt", null, "\u6700\u8FD1\u68C0\u67E5"),
h3("dd", null, checkedTime(account.health.lastCheckedAt))
)
),
h3(
"div",
{ className: "ddt-accountFooter dim-cardFooter" },
summary ? h3("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
h3(
"div",
{ className: "ddt-actions dim-cardActions" },
h3(
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"
),
h3(
Button,
{ className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) },
"\u79FB\u9664\u63A5\u5165"
)
)
)
),
removing ? h3(RemoveConfirmation, {
account,
busy: busy === "delete",
onConfirm: onConfirmRemove,
onCancel: onCancelRemove
}) : null
);
}
function AccountList(props) {
return h3(
"section",
{ className: "dim-listSection" },
h3(
"div",
{ className: "ddt-listHeading dim-listHeading" },
h3("h3", null, "\u5DF2\u63A5\u5165\u7684\u9489\u9489\u673A\u5668\u4EBA")
),
h3("ul", { className: "ddt-list dim-botList" }, props.bots.map((account) => h3(
"li",
{ key: account.botId },
h3(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] = React3.useState({
phase: "loading",
bots: [],
totals: EMPTY_TOTALS,
revision: 0,
error: null
});
const [provision, setProvision] = React3.useState(null);
const [busy, setBusy] = React3.useState(false);
const [busyByBot, setBusyByBot] = React3.useState({});
const [removeTarget, setRemoveTarget] = React3.useState(null);
const [credentialOpen, setCredentialOpen] = React3.useState(false);
const [credentialError, setCredentialError] = React3.useState(null);
const [notice, setNotice] = React3.useState("");
const [now, setNow] = React3.useState(() => Date.now());
const addButtonRef = React3.useRef(null);
const mountedRef = React3.useRef(true);
const statusRequestRef = React3.useRef(0);
const noticeFrameRef = React3.useRef(null);
const focusFrameRef = React3.useRef(null);
React3.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;
}
};
}, []);
React3.useEffect(() => installDingtalkStyles(), []);
const announce = React3.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 = React3.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 = React3.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 = React3.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]);
React3.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
return () => controller.abort();
}, [loadStatus]);
React3.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]);
React3.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 = React3.useCallback(async ({ replace = false } = {}) => {
if (!mountedRef.current) return;
setCredentialOpen(false);
setCredentialError(null);
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 bindCredentials = React3.useCallback(async ({ identity, secret }) => {
if (!mountedRef.current) return;
setBusy(true);
setCredentialError(null);
try {
const snapshot = normalizeSnapshot(await invoke(
DINGTALK_ENDPOINTS.bindCredentials,
{ clientId: identity, clientSecret: secret }
));
if (!mountedRef.current) return;
setModel({
phase: "ready",
bots: snapshot.bots,
totals: snapshot.totals,
revision: snapshot.revision,
error: null
});
setCredentialOpen(false);
announce("\u9489\u9489\u673A\u5668\u4EBA\u51ED\u636E\u5DF2\u7ED1\u5B9A\u3002");
} catch (error) {
if (mountedRef.current) setCredentialError(presentError(error));
} finally {
if (mountedRef.current) setBusy(false);
}
}, [announce, invoke]);
const cancelProvisioning = React3.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]);
React3.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 = React3.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 = React3.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 = React3.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 = React3.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 = h3(
"div",
{ className: "ddt-card ddt-loading", "aria-busy": "true" },
h3("div", { className: "ddt-spinner" }),
h3("span", null, "\u6B63\u5728\u7533\u8BF7\u9489\u9489\u6388\u6743\u4E8C\u7EF4\u7801\u2026")
);
} else if (provision?.status === "pending") {
provisionView = h3(QrPanel, {
provision,
now,
busy,
onRefresh: () => void startProvisioning({ replace: true }),
onCancel: () => void cancelProvisioning()
});
} else if (["scanned", "authorizing", "creating", "connecting"].includes(provision?.status)) {
provisionView = h3(ProgressPanel, {
status: provision.status,
busy,
onCancel: () => void cancelProvisioning()
});
} else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) {
provisionView = h3(ProvisionError, {
provision,
busy,
onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }),
onClose: () => void cancelProvisioning()
});
}
const credentialView = credentialOpen ? h3(CredentialBindingPanel, {
channel: "\u9489\u9489",
identityLabel: "Client ID",
identityPlaceholder: "\u586B\u5199\u9489\u9489\u5E94\u7528 Client ID",
secretLabel: "Client Secret",
secretPlaceholder: "\u586B\u5199\u9489\u9489\u5E94\u7528 Client Secret",
busy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : null;
return h3(
"section",
{ className: "ddt-page dim-channelPage", "aria-label": "\u9489\u9489\u8BBE\u7F6E" },
h3(Heading, {
totals: model.totals,
adding: Boolean(provision),
busy,
onAdd: () => void startProvisioning(),
onCredential: () => {
setCredentialOpen((value) => !value);
setCredentialError(null);
},
credentialOpen,
addButtonRef
}),
h3("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice),
model.error && model.phase === "ready" ? h3("div", { className: "ddt-statusNotice dim-statusNotice", role: "alert" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null,
model.phase === "loading" ? h3(LoadingView) : model.phase === "error" ? h3(
"div",
{ className: "ddt-card dim-surfaceCard" },
h3(
"div",
{ className: "ddt-inlineError dim-inlineError", role: "alert" },
h3("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u9489\u9489\u673A\u5668\u4EBA\u72B6\u6001"),
h3("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"),
h3(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
)
) : h3(
React3.Fragment,
null,
credentialView,
provisionView,
model.bots.length === 0 && !provision && !credentialOpen ? h3(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
model.bots.length > 0 ? h3(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/shared/token-api.js
var ACCOUNT_STATES2 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
function isRecord2(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function text(value, fallback, max = 240) {
return typeof value === "string" && value.trim() ? value.trim().slice(0, max) : fallback;
}
function id(value) {
const result = text(value, "", 128);
return /^[a-z\d_-]+$/i.test(result) ? result : void 0;
}
function timestamp2(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
return Number.isNaN(parsed) ? void 0 : parsed;
}
var TOKEN_BOT_ENDPOINTS = Object.freeze({
status: "connection.status",
bindCredentials: "bot.bind-credentials",
reconnectBot: "bot.reconnect",
deleteBot: "bot.delete"
});
function createTokenChannelApi(channel3, connectionSummary) {
const unwrapRpcResult9 = (result) => {
if (!isRecord2(result) || typeof result.ok !== "boolean") {
throw new Error(`${channel3} \u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94`);
}
if (!result.ok) {
const error = new Error(text(result.error?.message, `${channel3} \u64CD\u4F5C\u5931\u8D25`));
error.code = text(result.error?.code, `${channel3.toUpperCase()}_RPC_ERROR`, 80);
throw error;
}
return result.value;
};
const normalizeBot7 = (value) => {
if (!isRecord2(value) || !id(value.botId)) return void 0;
const connected = value.connected === true;
const state = ACCOUNT_STATES2.has(value.state) ? value.state : "offline";
return {
botId: id(value.botId),
connected,
state: connected ? "connected" : state,
bot: {
name: text(value.bot?.name, `${channel3}\u673A\u5668\u4EBA`, 100),
username: text(value.bot?.username, "", 100),
idMasked: text(value.bot?.idMasked, "\u673A\u5668\u4EBA\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
},
health: {
summary: text(
value.health?.summary,
connected ? `${channel3}${connectionSummary}\u8FD0\u884C\u6B63\u5E38` : `${channel3}\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA`
),
lastCheckedAt: timestamp2(value.health?.lastCheckedAt)
},
error: isRecord2(value.error) ? {
code: text(value.error.code, `${channel3.toUpperCase()}_ACCOUNT_ERROR`, 80),
message: text(value.error.message, `${channel3}\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA`)
} : null
};
};
const normalizeSnapshot8 = (value) => {
const source = isRecord2(value?.snapshot) ? value.snapshot : value;
if (!isRecord2(source) || !Array.isArray(source.bots)) {
throw new Error(`${channel3} \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868`);
}
const bots = source.bots.map(normalizeBot7).filter(Boolean);
return {
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
bots,
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length }
};
};
const presentError9 = (error) => ({
code: text(error?.code, `${channel3.toUpperCase()}_ERROR`, 80),
message: text(error?.message, `${channel3}\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`)
});
return Object.freeze({ unwrapRpcResult: unwrapRpcResult9, normalizeSnapshot: normalizeSnapshot8, presentError: presentError9 });
}
// plugin-src/client/channels/discord/api.js
var DISCORD_RPC_CHANNEL = "/discord";
var DISCORD_ENDPOINTS = TOKEN_BOT_ENDPOINTS;
var api = createTokenChannelApi("Discord", " Gateway \u957F\u8FDE\u63A5");
var unwrapRpcResult2 = api.unwrapRpcResult;
var normalizeSnapshot2 = api.normalizeSnapshot;
var presentError2 = api.presentError;
// plugin-src/client/channels/shared/token-channel.js
var React4 = __toESM(require("react"), 1);
var h4 = React4.createElement;
var Button3 = React4.forwardRef(function Button4({ children, kind = "secondary", className = "", ...props }, ref) {
return h4("button", {
...props,
ref,
type: "button",
className: `ddt-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function checkedTime2(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 createTokenChannelSettings(definition) {
const {
channel: channel3,
endpoints,
api: api3,
LogoGlyph,
installStyles,
pageClass,
avatarClass,
connectionLabel,
tokenPlaceholder,
emptyTitle,
emptyDescription,
platformLabel,
CredentialPanel = null,
credentialPayload = ({ secret }) => ({ token: secret }),
credentialAriaLabel = `\u4F7F\u7528 Bot Token \u63A5\u5165 ${channel3} \u673A\u5668\u4EBA`,
credentialOpenLabel = "\u624B\u52A8\u63A5\u5165",
credentialCloseLabel = "\u6536\u8D77\u51ED\u636E",
credentialNoun = "Bot Token",
emptyActionLabel = "\u586B\u5199 Bot Token"
} = definition;
function AccountCard5({ 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";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
const identity = account.bot.username ? `@${account.bot.username}` : account.bot.idMasked;
return h4(
"article",
{ className: "ddt-card dim-botCard", "data-bot-id": account.botId },
h4(
"div",
{ className: "ddt-cardBody dim-botCardBody" },
h4(
"div",
{ className: "ddt-accountTop dim-botCardTop" },
h4(
"div",
{ className: "ddt-accountIdentity dim-botIdentity" },
h4(
"div",
{ className: `ddt-avatar dim-botAvatar ${avatarClass}`, "aria-hidden": "true" },
h4(LogoGlyph, { size: 29 })
),
h4(
"div",
{ className: "dim-botName" },
h4("h3", null, account.bot.name),
h4("p", null, identity)
)
),
h4(
"div",
{ className: "ddt-health dim-botHealth" },
h4("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }),
h4("span", null, stateLabel)
)
),
h4(
"dl",
{ className: "ddt-metrics dim-botMetrics" },
h4(
"div",
{ className: "ddt-metric dim-botMetric" },
h4("dt", null, "\u6D88\u606F\u901A\u9053"),
h4("dd", null, account.connected ? connectionLabel : "\u79BB\u7EBF")
),
h4(
"div",
{ className: "ddt-metric dim-botMetric" },
h4("dt", null, "\u6700\u8FD1\u68C0\u67E5"),
h4("dd", null, checkedTime2(account.health.lastCheckedAt))
)
),
h4(
"div",
{ className: "ddt-accountFooter dim-cardFooter" },
summary ? h4("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
h4(
"div",
{ className: "ddt-actions dim-cardActions" },
h4(Button3, {
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(Button3, {
className: "dim-cardAction",
kind: "danger",
onClick: onRequestRemove,
disabled: Boolean(busy)
}, "\u79FB\u9664\u63A5\u5165")
)
)
),
removing ? h4(
"div",
{ className: "ddt-confirm dim-confirm", role: "alertdialog" },
h4("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`),
h4("p", null, `\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 ${credentialNoun}\u3001\u673A\u5668\u4EBA\u914D\u7F6E\u53CA\u4F1A\u8BDD\u6620\u5C04\u3002${platformLabel}\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002`),
h4(
"div",
{ className: "ddt-actions dim-viewActions" },
h4(Button3, { onClick: onCancelRemove, disabled: Boolean(busy) }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h4(
Button3,
{ kind: "danger", onClick: onConfirmRemove, disabled: Boolean(busy) },
busy === "delete" ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165"
)
)
) : null
);
}
function SettingsTab({ rpcCall }) {
const [model, setModel] = React4.useState({
phase: "loading",
bots: [],
totals: { configured: 0, connected: 0 },
error: null
});
const [credentialOpen, setCredentialOpen] = React4.useState(false);
const [credentialError, setCredentialError] = React4.useState(null);
const [busy, setBusy] = React4.useState(false);
const [busyByBot, setBusyByBot] = React4.useState({});
const [removeTarget, setRemoveTarget] = React4.useState(null);
const mounted = React4.useRef(true);
React4.useEffect(() => {
const disposeDingtalk = installDingtalkStyles();
const disposeChannel = installStyles();
mounted.current = true;
return () => {
mounted.current = false;
disposeChannel();
disposeDingtalk();
};
}, []);
const invoke = React4.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new TypeError(`${channel3} \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5`);
return api3.unwrapRpcResult(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React4.useCallback(async ({ signal, silent = false } = {}) => {
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
try {
const snapshot = api3.normalizeSnapshot(await invoke(endpoints.status, {}, signal));
if (!mounted.current || signal?.aborted) return;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
} catch (error) {
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
setModel((current) => ({
...current,
phase: silent ? current.phase : "error",
error: api3.presentError(error)
}));
}
}
}, [invoke]);
React4.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal });
return () => controller.abort();
}, [loadStatus]);
React4.useEffect(() => {
if (model.phase !== "ready") return void 0;
const controller = new AbortController();
const timer = window.setInterval(
() => void loadStatus({ signal: controller.signal, silent: true }),
15e3
);
return () => {
controller.abort();
window.clearInterval(timer);
};
}, [loadStatus, model.phase]);
const bindCredentials = React4.useCallback(async (values) => {
setBusy(true);
setCredentialError(null);
try {
const snapshot = api3.normalizeSnapshot(await invoke(
endpoints.bindCredentials,
credentialPayload(values)
));
if (!mounted.current) return;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
setCredentialOpen(false);
} catch (error) {
if (mounted.current) setCredentialError(api3.presentError(error));
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke]);
const botAction = React4.useCallback(async (account, operation, endpoint, payload) => {
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
try {
const snapshot = api3.normalizeSnapshot(await invoke(endpoint, payload));
if (mounted.current) {
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
}
} finally {
if (mounted.current) setBusyByBot((current) => {
const next = { ...current };
delete next[account.botId];
return next;
});
}
}, [invoke]);
const botList = model.bots.length > 0 ? h4(
"section",
{ className: "dim-listSection" },
h4(
"div",
{ className: "ddt-listHeading dim-listHeading" },
h4("h3", null, `\u5DF2\u63A5\u5165\u7684 ${channel3} \u673A\u5668\u4EBA`)
),
h4("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h4("li", { key: account.botId }, h4(AccountCard5, {
account,
busy: busyByBot[account.botId],
removing: removeTarget === account.botId,
onReconnect: () => void botAction(
account,
"reconnect",
endpoints.reconnectBot,
{ botId: account.botId }
),
onRequestRemove: () => setRemoveTarget(account.botId),
onCancelRemove: () => setRemoveTarget(null),
onConfirmRemove: async () => {
await botAction(account, "delete", endpoints.deleteBot, {
botId: account.botId,
confirm: true
});
if (mounted.current) setRemoveTarget(null);
}
}))))
) : null;
return h4(
"section",
{
className: `ddt-page ${pageClass} dim-channelPage`,
"aria-label": `${channel3} \u8BBE\u7F6E`
},
h4(
"div",
{ className: "ddt-heading" },
h4(
"div",
{ className: "ddt-tools" },
h4(
"div",
{ className: "dim-bindActions" },
h4(Button3, {
kind: "credential",
className: "dim-credentialButton",
onClick: () => {
setCredentialOpen((value) => !value);
setCredentialError(null);
},
disabled: busy,
"aria-pressed": credentialOpen,
"aria-label": credentialAriaLabel
}, h4(CredentialActionIcon), credentialOpen ? credentialCloseLabel : credentialOpenLabel)
),
model.totals.configured > 0 ? h4(
"div",
{ className: "ddt-badge dim-onlineBadge" },
h4("span", null, `${model.totals.connected} / ${model.totals.configured} \u5728\u7EBF`)
) : null
)
),
model.phase === "loading" ? h4("div", {
className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView",
"aria-busy": "true"
}, h4("div", { className: "ddt-spinner dim-spinner" }), `\u6B63\u5728\u8BFB\u53D6 ${channel3} \u673A\u5668\u4EBA\u72B6\u6001\u2026`) : model.phase === "error" ? h4(
"div",
{ className: "ddt-card dim-surfaceCard" },
h4(
"div",
{ className: "ddt-inlineError dim-inlineError" },
h4("h3", null, `\u65E0\u6CD5\u8BFB\u53D6 ${channel3} \u673A\u5668\u4EBA\u72B6\u6001`),
h4("p", null, model.error?.message),
h4(Button3, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
)
) : h4(
React4.Fragment,
null,
credentialOpen ? CredentialPanel ? h4(CredentialPanel, {
busy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : h4(CredentialBindingPanel, {
channel: channel3,
secretLabel: "Bot Token",
secretPlaceholder: tokenPlaceholder,
busy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : null,
model.bots.length === 0 && !credentialOpen ? h4(
"div",
{ className: "ddt-card dim-surfaceCard" },
h4(
"div",
{ className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" },
h4(
"div",
{ className: "dim-emptyCopy" },
h4(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h4("span", { className: "ddt-dot dim-stateDot" }),
h4("span", null, `\u5C1A\u672A\u63A5\u5165 ${channel3} \u673A\u5668\u4EBA`)
),
h4("h3", null, emptyTitle),
h4("p", null, emptyDescription),
h4(
"div",
{ className: "ddt-actions dim-viewActions" },
h4(Button3, {
kind: "primary",
onClick: () => setCredentialOpen(true)
}, emptyActionLabel)
)
),
h4("div", {
className: `ddt-brandMark dim-emptyBrand ${avatarClass}`,
"aria-hidden": "true"
}, h4(LogoGlyph, { size: 64 }))
)
) : null,
botList
)
);
}
return { SettingsTab, AccountCard: AccountCard5 };
}
// plugin-src/client/channels/discord/styles.js
var DISCORD_STYLE_ID = "xmanrui-dsh-im-discord-settings";
var CSS2 = String.raw`
.ddc-page { --ddt-accent: #5865f2; --ddt-accent-deep: #4752c4; --ddt-accent-wash: #eef0ff; }
.ddc-avatar { color: #fff; background: #5865f2; }
.ddc-avatar svg { display: block; }
`;
function installDiscordStyles() {
if (typeof document === "undefined") return () => {
};
const existing = document.querySelector(`style[data-plugin-css="${DISCORD_STYLE_ID}"]`);
if (existing) return () => {
};
const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im";
style.dataset.pluginCss = DISCORD_STYLE_ID;
style.textContent = CSS2;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/discord/index.js
var channel = createTokenChannelSettings({
channel: "Discord",
endpoints: DISCORD_ENDPOINTS,
api,
LogoGlyph: DiscordLogoGlyph,
installStyles: installDiscordStyles,
pageClass: "ddc-page",
avatarClass: "ddc-avatar",
connectionLabel: "Gateway \u957F\u8FDE\u63A5",
tokenPlaceholder: "\u586B\u5199 Discord Developer Portal \u7684 Bot Token",
emptyTitle: "\u63A5\u5165 Discord \u673A\u5668\u4EBA",
emptyDescription: "\u5148\u5728 Developer Portal \u521B\u5EFA Bot \u5E76\u9080\u8BF7\u5230\u670D\u52A1\u5668\uFF0C\u518D\u5728\u8FD9\u91CC\u5B8C\u6210\u63A5\u5165\u3002",
platformLabel: "Discord Developer Portal"
});
var DiscordSettingsTab = channel.SettingsTab;
var DiscordAccountCard = channel.AccountCard;
// plugin-src/client/channels/feishu/index.js
var React6 = __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",
bindCredentials: "bot.bind-credentials",
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 isRecord3(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 unwrapRpcResult3(result) {
if (!isRecord3(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 = isRecord3(value?.provisioning) ? value.provisioning : value;
if (!isRecord3(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 = isRecord3(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 = isRecord3(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 (!isRecord3(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 (!isRecord3(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 (!isRecord3(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 (!isRecord3(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 (!isRecord3(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" && isRecord3(value.connection)) {
normalized.connection = value.connection.botId ? normalizeBotConnection(value.connection) : normalizeConnectionSnapshot(value.connection);
}
return normalized;
}
function presentError3(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 React5 = __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 = React5.useRef(null);
React5.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 React5.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 CSS3 = 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-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 = CSS3;
document.head.appendChild(style);
return () => {
style.remove();
};
}
// plugin-src/client/channels/feishu/index.js
var h5 = React6.createElement;
function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) {
return h5("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 h5(
SvgIcon,
{ size },
h5("rect", {
x: "5",
y: "7.5",
width: "14",
height: "11",
rx: "4",
stroke: "currentColor",
strokeWidth: "1.7"
}),
h5("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 h5(
SvgIcon,
{ size },
h5("path", {
d: "M12 3.4 21 19H3L12 3.4Z",
stroke: "currentColor",
strokeWidth: "1.7",
strokeLinejoin: "round"
}),
h5("path", {
d: "M12 9v4.4M12 16.6v.01",
stroke: "currentColor",
strokeWidth: "1.9",
strokeLinecap: "round"
})
);
}
function QrIcon({ size = 58 }) {
return h5(SvgIcon, { size }, h5("path", {
d: "M4 4h6v6H4V4Zm10 0h6v6h-6V4ZM4 14h6v6H4v-6Zm10 0h2v2h-2v-2Zm4 0h2v4h-2v-4Zm-4 4h4v2h-4v-2Z",
fill: "currentColor"
}));
}
var Button5 = React6.forwardRef(function Button6({ children, kind = "secondary", size, icon, className = "", ...props }, ref) {
return h5("button", {
...props,
ref,
type: "button",
className: `bxf-button ${className}`.trim(),
"data-kind": kind,
"data-size": size
}, icon, h5("span", null, children));
});
function BrandMark() {
return h5("div", { className: "bxf-brandMark" }, h5(RobotIcon, { size: 34 }));
}
function Heading2({ totals, onAdd, onCredential, credentialOpen, adding, busy, addButtonRef }) {
const hasBots = totals.configured > 0;
return h5(
"div",
{ className: "bxf-heading" },
h5(
"div",
{ className: "bxf-headingTools" },
h5(
"div",
{ className: "dim-bindActions" },
h5(Button5, {
kind: "primary",
size: "small",
className: "bxf-bindButton dim-scanButton",
onClick: onAdd,
disabled: adding || busy,
ref: addButtonRef,
"aria-busy": busy ? "true" : void 0,
"aria-label": "\u626B\u7801\u63A5\u5165\u98DE\u4E66\u673A\u5668\u4EBA",
icon: h5(QrActionIcon)
}, adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA"),
h5(Button5, {
kind: "credential",
size: "small",
className: "dim-credentialButton",
onClick: onCredential,
disabled: adding || busy,
"aria-pressed": credentialOpen,
"aria-label": "\u4F7F\u7528 App ID \u548C App Secret \u7ED1\u5B9A\u98DE\u4E66\u673A\u5668\u4EBA",
icon: h5(CredentialActionIcon)
}, credentialOpen ? "\u6536\u8D77\u51ED\u636E" : "\u624B\u52A8\u63A5\u5165")
),
hasBots ? h5("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`
}, h5("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)) : null
)
);
}
function LoadingView2() {
return h5(
"div",
{
className: "bxf-card dim-surfaceCard dim-loadingView",
"aria-busy": "true",
"aria-label": "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u5217\u8868"
},
h5("div", { className: "dim-spinner", "aria-hidden": "true" }),
h5("span", null, "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u8FDE\u63A5\u72B6\u6001\u2026")
);
}
function EmptyView2({ onStart, busy }) {
return h5(
"div",
{ className: "bxf-card dim-surfaceCard" },
h5(
"div",
{ className: "bxf-cardBody bxf-intro dim-surfaceBody dim-emptyView" },
h5(
"div",
{ className: "bxf-introCopy dim-emptyCopy" },
h5(
"div",
{ className: "bxf-stateLabel dim-stateLabel" },
h5("span", { className: "bxf-dot dim-stateDot" }),
h5("span", null, "\u5C1A\u672A\u63A5\u5165\u673A\u5668\u4EBA")
),
h5("h3", null, "\u626B\u7801\uFF0C\u521B\u5EFA\u7B2C\u4E00\u4E2A\u98DE\u4E66\u5165\u53E3"),
h5("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"),
h5(
"div",
{ className: "bxf-actions dim-viewActions" },
h5(Button5, {
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")
)
),
h5("div", { className: "bxf-markStage dim-emptyBrand", "aria-hidden": "true" }, h5(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] = React6.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));
React6.useEffect(() => setImageFailed(false), [qrSource]);
return h5(
"div",
{ className: "bxf-card bxf-provisionCard dim-surfaceCard" },
h5(
"div",
{ className: "bxf-cardBody bxf-qrLayout dim-surfaceBody dim-qrLayout" },
h5(
"div",
{ className: "bxf-qrColumn dim-qrColumn" },
h5(
"div",
{ className: "bxf-qrFrame dim-qrFrame" },
qrSource && !imageFailed ? h5("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)
}) : h5(
"div",
{ className: "bxf-qrFallback dim-qrFallback" },
h5("div", null, h5(QrIcon), h5("span", null, "\u4E8C\u7EF4\u7801\u672A\u5C31\u7EEA\uFF0C\u8BF7\u6253\u5F00\u6388\u6743\u94FE\u63A5"))
),
expired ? h5(
"div",
{ className: "bxf-expiredOverlay dim-qrExpired", role: "status" },
h5("div", null, "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548", h5("br"), "\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u626B\u7801")
) : null
),
h5(
"div",
{
className: "bxf-countdown dim-countdown",
"aria-label": expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : `\u4E8C\u7EF4\u7801\u5269\u4F59 ${formatRemaining2(remaining)}`
},
h5(
"div",
{ className: "bxf-countdownTop dim-countdownTop", "aria-hidden": "true" },
h5("span", null, expired ? "\u7B49\u5F85\u5237\u65B0" : "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"),
h5("strong", null, formatRemaining2(remaining))
),
h5(
"div",
{ className: "bxf-progress dim-progress", "aria-hidden": "true" },
h5("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } })
)
)
),
h5(
"div",
{ className: "bxf-qrCopy dim-qrCopy" },
h5(
"div",
{ className: "bxf-stateLabel dim-stateLabel" },
h5("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
h5("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA")
),
h5("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
h5("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"),
h5(
"ol",
{ className: "bxf-steps dim-steps" },
h5("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"),
h5("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
h5("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA")
),
h5(
"div",
{ className: "bxf-actions dim-viewActions" },
expired ? h5(Button5, {
kind: "primary",
onClick: onRefresh,
disabled: busy
}, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u4E8C\u7EF4\u7801") : href ? h5("a", {
className: "bxf-button bxf-link",
"data-kind": "secondary",
href,
target: "_blank",
rel: "noopener noreferrer"
}, h5("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null,
!expired ? h5(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
h5(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0")
)
)
)
);
}
function ProvisionProgress({ phase, onCancel, busy }) {
const connecting = phase === "connecting";
return h5(
"div",
{
className: "bxf-card bxf-provisionCard dim-surfaceCard dim-loadingView",
"aria-busy": "true"
},
h5("div", { className: "dim-spinner", "aria-hidden": "true" }),
h5("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"),
h5("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 ? h5(
"div",
{ className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
h5(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0")
) : null
);
}
function ProvisionError2({ error, onRetry, onCancel, busy }) {
return h5(
"div",
{ className: "bxf-card bxf-provisionCard dim-surfaceCard" },
h5(
"div",
{ className: "bxf-inlineError dim-inlineError", role: "alert" },
h5(
"div",
null,
h5("h3", null, "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"),
h5("p", null, error.message),
error.code ? h5("span", { className: "bxf-errorCode" }, error.code) : null,
h5(
"div",
{ className: "bxf-actions dim-viewActions" },
h5(
Button5,
{ kind: "primary", onClick: onRetry, disabled: busy },
busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"
),
h5(Button5, { 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(timestamp7) {
if (!timestamp7) return "\u5C1A\u672A\u68C0\u67E5";
try {
return new Intl.DateTimeFormat("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}).format(new Date(timestamp7));
} catch {
return "\u521A\u521A";
}
}
function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
const cancelRef = React6.useRef(null);
const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-");
const titleId = `bxf-remove-title-${idPart}`;
const descriptionId = `bxf-remove-description-${idPart}`;
React6.useEffect(() => cancelRef.current?.focus(), []);
return h5(
"div",
{
className: "bxf-confirm dim-confirm",
role: "alertdialog",
"aria-labelledby": titleId,
"aria-describedby": descriptionId,
onKeyDown: (event) => {
if (event.key === "Escape" && !busy) {
event.preventDefault();
onCancel();
}
}
},
h5("h4", { id: titleId }, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${bot.bot.name}\u201D\uFF1F`),
h5(
"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"
),
h5(
"div",
{ className: "bxf-actions dim-viewActions" },
h5(Button5, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h5(
Button5,
{ 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 ?? (connected ? null : health.summary);
const titleId = `bxf-bot-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
return h5(
"article",
{
className: "bxf-card bxf-botCard dim-botCard",
"aria-labelledby": titleId,
"data-bot-id": connection.botId,
tabIndex: -1,
ref: cardRef
},
h5(
"div",
{ className: "bxf-cardBody dim-botCardBody" },
h5(
"div",
{ className: "bxf-connectedTop dim-botCardTop" },
h5(
"div",
{ className: "bxf-botIdentity dim-botIdentity" },
h5(
"div",
{ className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" },
h5(FeishuLogoGlyph, { size: 34 })
),
h5(
"div",
{ className: "bxf-botName dim-botName" },
h5("h3", { id: titleId, title: bot.name }, bot.name),
h5("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58")
)
),
h5(
"div",
{ className: "bxf-healthPill dim-botHealth", "data-health": stateForDisplay },
h5("span", { className: "bxf-dot dim-healthDot", "data-tone": tone }),
h5("span", null, HEALTH_LABELS[stateForDisplay] ?? "\u72B6\u6001\u672A\u77E5")
)
),
h5(
"dl",
{ className: "bxf-statusGrid dim-botMetrics" },
h5(
"div",
{ className: "bxf-metric dim-botMetric" },
h5("dt", null, "\u6D88\u606F\u901A\u9053"),
h5("dd", null, connected ? "\u957F\u8FDE\u63A5" : stateForDisplay === "connecting" ? "\u8FDE\u63A5\u4E2D" : "\u5DF2\u65AD\u5F00")
),
h5(
"div",
{ className: "bxf-metric dim-botMetric" },
h5("dt", null, "\u6700\u8FD1\u68C0\u67E5"),
h5("dd", null, formatCheckedTime(health.lastCheckedAt))
)
),
h5(
"div",
{ className: "bxf-connectedFooter dim-cardFooter" },
summary ? h5(
"div",
{ className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 },
summary
) : null,
h5(
"div",
{ className: "bxf-actions bxf-botActions dim-cardActions" },
h5(Button5, {
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"),
h5(Button5, {
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 ? h5(RemoveConfirmation2, {
bot: connection,
busy: busy === "delete",
onConfirm: onConfirmRemove,
onCancel: onCancelRemove
}) : null
);
}
function BotList(props) {
return h5(
"section",
{ className: "bxf-listSection dim-listSection", "aria-labelledby": "bxf-bot-list-title" },
h5(
"div",
{ className: "bxf-listHeading dim-listHeading" },
h5("h3", { id: "bxf-bot-list-title" }, "\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA")
),
h5(
"ul",
{ className: "bxf-botList dim-botList", role: "list" },
props.bots.map((bot) => h5(
"li",
{ key: bot.botId },
h5(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 h5(
"div",
{ className: "bxf-card dim-surfaceCard" },
h5(
"div",
{ className: "bxf-error dim-inlineError", role: "alert" },
h5(
"div",
null,
h5("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA"),
h5("p", null, error.message),
error.code ? h5("span", { className: "bxf-errorCode" }, error.code) : null,
h5(
"div",
{ className: "bxf-actions dim-viewActions" },
h5(
Button5,
{ 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] = React6.useState({
phase: "loading",
revision: 0,
bots: [],
totals: EMPTY_TOTALS2,
provisioning: null,
pageError: null,
statusError: null
});
const [pageBusy, setPageBusy] = React6.useState(false);
const [provisionBusy, setProvisionBusy] = React6.useState(false);
const [credentialOpen, setCredentialOpen] = React6.useState(false);
const [credentialBusy, setCredentialBusy] = React6.useState(false);
const [credentialError, setCredentialError] = React6.useState(null);
const [busyByBot, setBusyByBot] = React6.useState({});
const [errorsByBot, setErrorsByBot] = React6.useState({});
const [removeTargetId, setRemoveTargetId] = React6.useState(null);
const [announcement, setAnnouncement] = React6.useState("");
const [now, setNow] = React6.useState(() => Date.now());
const [focusBotId, setFocusBotId] = React6.useState(null);
const cardRefs = React6.useRef(/* @__PURE__ */ new Map());
const removeButtonRefs = React6.useRef(/* @__PURE__ */ new Map());
const addButtonRef = React6.useRef(null);
const scheduleAnimationFrame = useAnimationFrameScheduler();
const announce = React6.useCallback((message) => {
setAnnouncement("");
scheduleAnimationFrame(() => {
if (message) setAnnouncement(message);
}, "announcement");
}, [scheduleAnimationFrame]);
const invoke = React6.useCallback(async (endpoint, payload = {}, signal) => {
return unwrapRpcResult3(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const mergeSnapshot = React6.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
const now2 = Date.now();
setModel((current) => mergeFeishuSnapshotState(
current,
snapshot,
{ restoreProvisioning, now: now2 }
));
}, []);
const loadStatus = React6.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 = presentError3(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]);
React6.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
return () => controller.abort();
}, [loadStatus]);
React6.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]);
React6.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 = React6.useCallback(async ({ replace = false } = {}) => {
setCredentialOpen(false);
setCredentialError(null);
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 timestamp7 = Date.now();
setNow(timestamp7);
setModel((current) => ({
...current,
provisioning: {
phase: "qr",
...provision2,
durationMs: Math.max(1, provision2.expiresAt - timestamp7),
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: presentError3(error) }
}));
} finally {
setProvisionBusy(false);
}
}, [announce, invoke, model.provisioning?.attemptId]);
const bindCredentials = React6.useCallback(async ({ identity, secret }) => {
setCredentialBusy(true);
setCredentialError(null);
try {
const snapshot = normalizeBotsSnapshot(await invoke(
FEISHU_ENDPOINTS.bindCredentials,
{ appId: identity, appSecret: secret }
));
mergeSnapshot(snapshot);
setCredentialOpen(false);
announce("\u98DE\u4E66\u673A\u5668\u4EBA\u51ED\u636E\u5DF2\u7ED1\u5B9A\u3002");
} catch (error) {
setCredentialError(presentError3(error));
} finally {
setCredentialBusy(false);
}
}, [announce, invoke, mergeSnapshot]);
const cancelProvisioning = React6.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: presentError3(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;
React6.useEffect(() => {
if (!countdownAttemptId || countdownPhase !== "qr" || countdownExpired) return void 0;
const tick = () => {
const timestamp7 = Date.now();
setNow(timestamp7);
if (timestamp7 >= 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]);
React6.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: presentError3(error)
}
} : current);
}
}, provision2.pollIntervalMs);
return () => {
controller.abort();
window.clearTimeout(timer);
};
}, [announce, invoke, loadStatus, model.provisioning]);
const setBotBusy = React6.useCallback((botId, value) => {
setBusyByBot((current) => {
const next = { ...current };
if (value) next[botId] = value;
else delete next[botId];
return next;
});
}, []);
const setBotError = React6.useCallback((botId, error) => {
setErrorsByBot((current) => {
const next = { ...current };
if (error) next[botId] = presentError3(error);
else delete next[botId];
return next;
});
}, []);
const reconnectOneBot = React6.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 = React6.useCallback((connection) => {
setRemoveTargetId(connection.botId);
}, []);
const cancelRemove = React6.useCallback(() => {
const botId = removeTargetId;
setRemoveTargetId(null);
scheduleAnimationFrame(() => removeButtonRefs.current.get(botId)?.focus(), "focus");
}, [removeTargetId, scheduleAnimationFrame]);
const confirmRemove = React6.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 = h5(ProvisionProgress, { phase: "creating", busy: provisionBusy });
} else if (provision?.phase === "qr") {
provisionContent = h5(QrPane, {
provision,
now,
onRefresh: () => void startProvisioning({ replace: true }),
onCancel: () => void cancelProvisioning(),
busy: provisionBusy || model.phase !== "ready"
});
} else if (provision?.phase === "connecting") {
provisionContent = h5(ProvisionProgress, {
phase: "connecting",
onCancel: () => void cancelProvisioning(),
busy: provisionBusy
});
} else if (provision?.phase === "error") {
provisionContent = h5(ProvisionError2, {
error: provision.error,
onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }),
onCancel: () => void cancelProvisioning(),
busy: provisionBusy
});
}
const credentialContent = credentialOpen ? h5(CredentialBindingPanel, {
channel: "\u98DE\u4E66",
identityLabel: "App ID",
identityPlaceholder: "\u586B\u5199\u98DE\u4E66\u5F00\u653E\u5E73\u53F0 App ID",
secretLabel: "App Secret",
secretPlaceholder: "\u586B\u5199\u98DE\u4E66\u5F00\u653E\u5E73\u53F0 App Secret",
busy: credentialBusy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : null;
const setCardRef = React6.useCallback((botId, node) => {
if (node) cardRefs.current.set(botId, node);
else cardRefs.current.delete(botId);
}, []);
const setRemoveButtonRef = React6.useCallback((botId, node) => {
if (node) removeButtonRefs.current.set(botId, node);
else removeButtonRefs.current.delete(botId);
}, []);
return h5(
"section",
{ className: "bxf-page dim-channelPage", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" },
h5(Heading2, {
totals: model.totals,
onAdd: () => void startProvisioning(),
onCredential: () => {
setCredentialOpen((value) => !value);
setCredentialError(null);
},
credentialOpen,
adding: Boolean(provision),
busy: provisionBusy || credentialBusy,
addButtonRef
}),
h5("div", {
className: "bxf-visuallyHidden",
role: "status",
"aria-live": "polite",
"aria-atomic": "true"
}, announcement),
model.statusError ? h5(
"div",
{ className: "bxf-statusNotice dim-statusNotice", role: "status" },
h5(AlertIcon, { size: 16 }),
h5("span", null, `\u72B6\u6001\u81EA\u52A8\u5237\u65B0\u5931\u8D25\uFF1A${model.statusError.message}`),
h5(Button5, { size: "small", onClick: () => void loadStatus({ silent: true }), disabled: pageBusy }, "\u7ACB\u5373\u91CD\u8BD5")
) : null,
model.phase === "loading" ? h5(LoadingView2) : model.phase === "error" ? h5(PageError, {
error: model.pageError ?? { message: "\u65E0\u6CD5\u8BFB\u53D6\u8FDE\u63A5\u72B6\u6001" },
onRetry: () => void loadStatus(),
busy: pageBusy
}) : h5(
React6.Fragment,
null,
credentialContent,
provisionContent,
model.bots.length === 0 && !provision && !credentialOpen ? h5(EmptyView2, { onStart: () => void startProvisioning(), busy: provisionBusy }) : null,
model.bots.length > 0 ? h5(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/qq/api.js
var QQ_RPC_CHANNEL = "/qq";
var QQ_ENDPOINTS = Object.freeze({
status: "connection.status",
beginProvisioning: "provision.begin",
pollProvisioning: "provision.poll",
cancelProvisioning: "provision.cancel",
bindCredentials: "bot.bind-credentials",
reconnectBot: "bot.reconnect",
deleteBot: "bot.delete"
});
var PROVISION_STATES2 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
var ACCOUNT_STATES3 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
var QR_DATA_URL2 = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
function isRecord4(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function text2(value, fallback, max = 240) {
return typeof value === "string" && value.trim() ? value.trim().slice(0, max) : fallback;
}
function id2(value) {
const result = text2(value, "", 128);
return /^[a-z\d_-]+$/i.test(result) ? result : void 0;
}
function timestamp3(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
return Number.isNaN(parsed) ? void 0 : parsed;
}
function unwrapRpcResult4(result) {
if (!isRecord4(result) || typeof result.ok !== "boolean") throw new Error("QQ \u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
if (!result.ok) {
const error = new Error(text2(result.error?.message, "QQ \u64CD\u4F5C\u5931\u8D25"));
error.code = text2(result.error?.code, "QQ_RPC_ERROR", 80);
throw error;
}
return result.value;
}
function safeQrSource3(value) {
return typeof value === "string" && value.length <= 2 * 1024 * 1024 && QR_DATA_URL2.test(value) ? value : void 0;
}
function normalizeProvisioning3(value, now = Date.now()) {
const source = isRecord4(value?.provisioning) ? value.provisioning : value;
if (!isRecord4(source)) throw new Error("QQ \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u7ED1\u5B9A\u8FDB\u5EA6");
const attemptId = id2(source.attemptId);
if (!attemptId) throw new Error("QQ \u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1");
const reported = text2(source.status, "failed", 32);
const result = {
attemptId,
status: PROVISION_STATES2.has(reported) ? reported : "failed",
expiresAt: timestamp3(source.expiresAt) ?? now + 5 * 6e4,
pollIntervalMs: Math.min(1e4, Math.max(500, Number(source.pollIntervalMs) || 1e3)),
qrRevision: Number.isSafeInteger(source.qrRevision) ? source.qrRevision : 0
};
const qrCodeDataUrl = safeQrSource3(source.qrCodeDataUrl);
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
if (id2(source.botId)) result.botId = id2(source.botId);
if (isRecord4(source.error)) result.error = {
code: text2(source.error.code, "QQ_PROVISION_FAILED", 80),
message: text2(source.error.message, "QQ \u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210")
};
return result;
}
function normalizeBot3(value) {
if (!isRecord4(value) || !id2(value.botId)) return void 0;
const connected = value.connected === true;
const state = ACCOUNT_STATES3.has(value.state) ? value.state : "offline";
return {
botId: id2(value.botId),
connected,
state: connected ? "connected" : state,
bot: {
name: text2(value.bot?.name, "QQ\u673A\u5668\u4EBA", 100),
appIdMasked: text2(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
},
health: {
summary: text2(value.health?.summary, connected ? "QQ WebSocket \u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "QQ \u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"),
lastCheckedAt: timestamp3(value.health?.lastCheckedAt)
},
error: isRecord4(value.error) ? {
code: text2(value.error.code, "QQ_ACCOUNT_ERROR", 80),
message: text2(value.error.message, "QQ \u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA")
} : null
};
}
function normalizeSnapshot3(value) {
const source = isRecord4(value?.snapshot) ? value.snapshot : value;
if (!isRecord4(source) || !Array.isArray(source.bots)) throw new Error("QQ \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
const bots = source.bots.map(normalizeBot3).filter(Boolean);
return {
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
bots,
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
provisioning: source.provisioning ? normalizeProvisioning3(source.provisioning) : null
};
}
function presentError4(error) {
return {
code: text2(error?.code, "QQ_ERROR", 80),
message: text2(error?.message, "QQ \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")
};
}
function formatRemaining3(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/qq/index.js
var React7 = __toESM(require("react"), 1);
// plugin-src/client/channels/qq/styles.js
var QQ_STYLE_ID = "xmanrui-dsh-im-qq-settings";
var CSS4 = String.raw`
.dqq-page { --ddt-accent: #1677ff; --ddt-accent-deep: #0958d9; --ddt-accent-wash: #eaf3ff; }
.dqq-avatar, .dqq-brand { color: #fff; background: #1677ff; }
.dqq-avatar svg, .dqq-brand svg { display: block; }
`;
function installQqStyles() {
if (typeof document === "undefined") return () => {
};
const existing = document.querySelector(`style[data-plugin-css="${QQ_STYLE_ID}"]`);
if (existing) return () => {
};
const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im";
style.dataset.pluginCss = QQ_STYLE_ID;
style.textContent = CSS4;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/qq/index.js
var h6 = React7.createElement;
var ACTIVE_STATES = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
var Button7 = React7.forwardRef(function Button8({ children, kind = "secondary", className = "", ...props }, ref) {
return h6("button", {
...props,
ref,
type: "button",
className: `ddt-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function checkedTime3(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 Heading3({ totals, adding, busy, onAdd, onCredential, credentialOpen, addButtonRef }) {
return h6(
"div",
{ className: "ddt-heading" },
h6(
"div",
{ className: "ddt-tools" },
h6(
"div",
{ className: "dim-bindActions" },
h6(Button7, {
kind: "primary",
className: "dim-scanButton",
onClick: onAdd,
disabled: adding || busy,
ref: addButtonRef,
"aria-label": "\u626B\u7801\u63A5\u5165 QQ \u673A\u5668\u4EBA"
}, h6(QrActionIcon), adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA"),
h6(Button7, {
kind: "credential",
className: "dim-credentialButton",
onClick: onCredential,
disabled: adding || busy,
"aria-pressed": credentialOpen,
"aria-label": "\u4F7F\u7528 AppID \u548C AppSecret \u7ED1\u5B9A QQ \u673A\u5668\u4EBA"
}, h6(CredentialActionIcon), credentialOpen ? "\u6536\u8D77\u51ED\u636E" : "\u624B\u52A8\u63A5\u5165")
),
totals.configured > 0 ? h6(
"div",
{ className: "ddt-badge dim-onlineBadge" },
h6("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)
) : null
)
);
}
function LoadingView3() {
return h6(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h6("div", { className: "ddt-spinner dim-spinner" }),
h6("span", null, "\u6B63\u5728\u8BFB\u53D6 QQ \u673A\u5668\u4EBA\u72B6\u6001\u2026")
);
}
function EmptyView3({ busy, onStart }) {
return h6(
"div",
{ className: "ddt-card dim-surfaceCard" },
h6(
"div",
{ className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" },
h6(
"div",
{ className: "dim-emptyCopy" },
h6(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h6("span", { className: "ddt-dot dim-stateDot" }),
h6("span", null, "\u5C1A\u672A\u7ED1\u5B9A QQ \u673A\u5668\u4EBA")
),
h6("h3", null, "\u4F7F\u7528\u624B\u673A QQ \u626B\u7801\u521B\u5EFA\u5E76\u7ED1\u5B9A\u673A\u5668\u4EBA"),
h6("p", null, "\u626B\u7801\u7531\u817E\u8BAF\u5B98\u65B9\u9875\u9762\u5B8C\u6210\uFF0C\u4E0D\u9700\u8981\u624B\u52A8\u586B\u5199 AppID \u6216 AppSecret\u3002\u626B\u7801\u6210\u529F\u540E\uFF0C\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u8FDE\u63A5 DeepSeek Harness\u3002"),
h6(
"div",
{ className: "ddt-actions dim-viewActions" },
h6(
Button7,
{ kind: "primary", onClick: onStart, disabled: busy },
busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210 QQ \u4E8C\u7EF4\u7801"
)
)
),
h6(
"div",
{ className: "ddt-brandMark dim-emptyBrand dqq-brand", "aria-hidden": "true" },
h6(QqLogoGlyph, { size: 64 })
)
)
);
}
function QrPanel2({ provision, now, busy, onRefresh, onCancel }) {
const source = safeQrSource3(provision.qrCodeDataUrl);
const remaining = Math.max(0, provision.expiresAt - now);
const duration = Math.max(1, provision.durationMs ?? 5 * 6e4);
const progress = Math.round(Math.min(1, remaining / duration) * 100);
const refreshing = provision.status === "refreshing";
return h6(
"div",
{ className: "ddt-card dim-surfaceCard" },
h6(
"div",
{ className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" },
h6(
"div",
{ className: "ddt-qrColumn dim-qrColumn" },
h6(
"div",
{ className: "ddt-qrFrame dim-qrFrame" },
source ? h6("img", { src: source, alt: "\u7528\u4E8E\u7ED1\u5B9A QQ \u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801" }) : h6(
"div",
{ className: "ddt-qrFallback dim-qrFallback" },
refreshing ? "\u4E8C\u7EF4\u7801\u6B63\u5728\u81EA\u52A8\u5237\u65B0\u2026" : "\u4E8C\u7EF4\u7801\u56FE\u7247\u6B63\u5728\u751F\u6210\u2026"
)
),
h6(
"div",
{ className: "ddt-countdown dim-countdown" },
h6(
"div",
{ className: "ddt-countdownTop dim-countdownTop" },
h6("span", null, "\u5F53\u524D\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"),
h6("strong", null, refreshing ? "--:--" : formatRemaining3(remaining))
),
h6("div", { className: "ddt-progress dim-progress", style: { "--ddt-progress": `${progress}%` } }, h6("span"))
)
),
h6(
"div",
{ className: "ddt-qrCopy dim-qrCopy" },
h6(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h6("span", { className: "ddt-dot dim-stateDot", "data-tone": "warning" }),
h6("span", null, refreshing ? "\u6B63\u5728\u5237\u65B0\u4E8C\u7EF4\u7801" : "\u7B49\u5F85\u624B\u673A QQ \u626B\u7801")
),
h6("h3", null, "\u4F7F\u7528\u624B\u673A QQ \u5B8C\u6210\u673A\u5668\u4EBA\u7ED1\u5B9A"),
h6("p", null, "\u817E\u8BAF\u9875\u9762\u4F1A\u521B\u5EFA\u6216\u7ED1\u5B9A\u4E00\u4E2A QQ \u673A\u5668\u4EBA\uFF0C\u5E76\u628A\u8FDE\u63A5\u51ED\u636E\u5B89\u5168\u4EA4\u7ED9\u672C\u673A Harness Host\u3002"),
h6(
"ol",
{ className: "ddt-steps dim-steps" },
h6("li", null, "\u6253\u5F00\u624B\u673A QQ\uFF0C\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"),
h6("li", null, "\u5728\u817E\u8BAF\u6388\u6743\u9875\u9762\u786E\u8BA4\u521B\u5EFA\u6216\u7ED1\u5B9A\u673A\u5668\u4EBA"),
h6("li", null, "\u8FD4\u56DE\u8FD9\u91CC\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210")
),
h6(
"div",
{ className: "ddt-actions dim-viewActions" },
h6(Button7, { onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h6(Button7, { kind: "quiet", onClick: onCancel, disabled: busy }, "\u53D6\u6D88")
)
)
)
);
}
function ProvisionView({ provision, busy, onRetry, onClose }) {
if (provision.status === "connecting") {
return h6(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-specialView", "aria-busy": "true" },
h6("div", { className: "ddt-spinner dim-spinner" }),
h6("h3", null, "QQ \u5DF2\u6388\u6743\uFF0C\u6B63\u5728\u8FDE\u63A5\u673A\u5668\u4EBA"),
h6("p", null, "\u51ED\u636E\u6B63\u5728\u5199\u5165\u672C\u673A\uFF0C\u5E76\u542F\u52A8 QQ WebSocket \u6D88\u606F\u8FDE\u63A5\u3002")
);
}
const error = provision.error ?? { code: "QQ_PROVISION_FAILED", message: "QQ \u673A\u5668\u4EBA\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210" };
return h6(
"div",
{ className: "ddt-card dim-surfaceCard" },
h6(
"div",
{ className: "ddt-inlineError dim-inlineError", role: "alert" },
h6("h3", null, "QQ \u673A\u5668\u4EBA\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"),
h6("p", null, error.message),
h6("span", { className: "ddt-errorCode" }, error.code),
h6(
"div",
{ className: "ddt-actions dim-viewActions" },
h6(Button7, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h6(Button7, { onClick: onClose, disabled: busy }, "\u5173\u95ED")
)
)
);
}
function RemoveConfirmation3({ account, busy, onConfirm, onCancel }) {
return h6(
"div",
{ className: "ddt-confirm dim-confirm", role: "alertdialog" },
h6("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`),
h6("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\u817E\u8BAF\u5E73\u53F0\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002"),
h6(
"div",
{ className: "ddt-actions dim-viewActions" },
h6(Button7, { onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h6(Button7, { kind: "danger", onClick: onConfirm, disabled: busy }, busy ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165")
)
);
}
function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) {
const tone = account.connected ? "success" : account.state === "error" ? "error" : "warning";
const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : account.state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
return h6(
"article",
{ className: "ddt-card dim-botCard", "data-bot-id": account.botId },
h6(
"div",
{ className: "ddt-cardBody dim-botCardBody" },
h6(
"div",
{ className: "ddt-accountTop dim-botCardTop" },
h6(
"div",
{ className: "ddt-accountIdentity dim-botIdentity" },
h6("div", { className: "ddt-avatar dim-botAvatar dqq-avatar", "aria-hidden": "true" }, h6(QqLogoGlyph, { size: 29 })),
h6(
"div",
{ className: "dim-botName" },
h6("h3", null, account.bot.name),
h6("p", null, account.bot.appIdMasked)
)
),
h6(
"div",
{ className: "ddt-health dim-botHealth" },
h6("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }),
h6("span", null, stateLabel)
)
),
h6(
"dl",
{ className: "ddt-metrics dim-botMetrics" },
h6("div", { className: "ddt-metric dim-botMetric" }, h6("dt", null, "\u6D88\u606F\u901A\u9053"), h6("dd", null, account.connected ? "WebSocket \u957F\u8FDE\u63A5" : "\u79BB\u7EBF")),
h6("div", { className: "ddt-metric dim-botMetric" }, h6("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h6("dd", null, checkedTime3(account.health.lastCheckedAt)))
),
h6(
"div",
{ className: "ddt-accountFooter dim-cardFooter" },
summary ? h6("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
h6(
"div",
{ className: "ddt-actions dim-cardActions" },
h6(Button7, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
h6(Button7, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
)
)
),
removing ? h6(RemoveConfirmation3, {
account,
busy: busy === "delete",
onConfirm: onConfirmRemove,
onCancel: onCancelRemove
}) : null
);
}
function QqSettingsTab({ rpcCall }) {
const [model, setModel] = React7.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
const [provision, setProvision] = React7.useState(null);
const [busy, setBusy] = React7.useState(false);
const [busyByBot, setBusyByBot] = React7.useState({});
const [removeTarget, setRemoveTarget] = React7.useState(null);
const [credentialOpen, setCredentialOpen] = React7.useState(false);
const [credentialError, setCredentialError] = React7.useState(null);
const [now, setNow] = React7.useState(Date.now());
const mounted = React7.useRef(true);
const addButtonRef = React7.useRef(null);
React7.useEffect(() => {
const disposeDingtalk = installDingtalkStyles();
const disposeQq = installQqStyles();
mounted.current = true;
return () => {
mounted.current = false;
disposeQq();
disposeDingtalk();
};
}, []);
const invoke = React7.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new TypeError("QQ \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
return unwrapRpcResult4(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React7.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
try {
const snapshot = normalizeSnapshot3(await invoke(QQ_ENDPOINTS.status, {}, signal));
if (!mounted.current || signal?.aborted) return void 0;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
if (restore && snapshot.provisioning) setProvision({
...snapshot.provisioning,
durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
});
return snapshot;
} catch (error) {
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
setModel((current) => ({ ...current, phase: silent ? current.phase : "error", error: presentError4(error) }));
}
return void 0;
}
}, [invoke]);
React7.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restore: true });
return () => controller.abort();
}, [loadStatus]);
React7.useEffect(() => {
if (model.phase !== "ready") return void 0;
const controller = new AbortController();
const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
return () => {
controller.abort();
window.clearInterval(timer);
};
}, [loadStatus, model.phase]);
React7.useEffect(() => {
if (!provision || !ACTIVE_STATES.has(provision.status)) return void 0;
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
return () => window.clearInterval(timer);
}, [provision?.attemptId, provision?.status]);
const startProvisioning = React7.useCallback(async (replace = false) => {
setCredentialOpen(false);
setCredentialError(null);
setBusy(true);
try {
if (replace && provision?.attemptId) await invoke(QQ_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
if (!mounted.current) return;
setProvision({ status: "starting" });
const started = normalizeProvisioning3(await invoke(QQ_ENDPOINTS.beginProvisioning, { locale: "zh-CN" }));
if (!mounted.current) return;
setNow(Date.now());
setProvision({ ...started, durationMs: Math.max(1, started.expiresAt - Date.now()) });
} catch (error) {
if (mounted.current) setProvision({ status: "failed", error: presentError4(error) });
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId]);
const bindCredentials = React7.useCallback(async ({ identity, secret }) => {
setBusy(true);
setCredentialError(null);
try {
const snapshot = normalizeSnapshot3(await invoke(
QQ_ENDPOINTS.bindCredentials,
{ appId: identity, appSecret: secret }
));
if (!mounted.current) return;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
setCredentialOpen(false);
} catch (error) {
if (mounted.current) setCredentialError(presentError4(error));
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke]);
const closeProvision = React7.useCallback(async () => {
setBusy(true);
try {
if (provision?.attemptId && ACTIVE_STATES.has(provision.status)) {
await invoke(QQ_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
}
if (mounted.current) setProvision(null);
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId, provision?.status]);
React7.useEffect(() => {
const attemptId = provision?.attemptId;
if (!attemptId || !ACTIVE_STATES.has(provision.status)) return void 0;
const controller = new AbortController();
let disposed = false;
let timer;
const poll = async () => {
try {
const current = normalizeProvisioning3(await invoke(QQ_ENDPOINTS.pollProvisioning, { attemptId }, controller.signal));
if (disposed || controller.signal.aborted || !mounted.current) return;
if (current.status === "connected") {
setProvision(null);
await loadStatus({ signal: controller.signal, silent: true });
return;
}
setProvision((previous) => previous?.attemptId === attemptId ? { ...previous, ...current, durationMs: current.qrRevision !== previous.qrRevision ? Math.max(1, current.expiresAt - Date.now()) : previous.durationMs } : previous);
if (ACTIVE_STATES.has(current.status)) timer = window.setTimeout(poll, current.pollIntervalMs);
} catch (error) {
if (!disposed && !controller.signal.aborted && mounted.current) {
setProvision((current) => ({ ...current, status: "failed", error: presentError4(error) }));
}
}
};
timer = window.setTimeout(poll, provision.pollIntervalMs ?? 1e3);
return () => {
disposed = true;
controller.abort();
window.clearTimeout(timer);
};
}, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
const botAction = React7.useCallback(async (account, operation, endpoint, payload) => {
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
try {
const snapshot = normalizeSnapshot3(await invoke(endpoint, payload));
if (mounted.current) setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
return snapshot;
} finally {
if (mounted.current) setBusyByBot((current) => {
const next = { ...current };
delete next[account.botId];
return next;
});
}
}, [invoke]);
let provisionView = null;
if (provision?.status === "starting") provisionView = h6("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h6("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7 QQ \u4E8C\u7EF4\u7801\u2026");
else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h6(QrPanel2, {
provision,
now,
busy,
onRefresh: () => void startProvisioning(true),
onCancel: () => void closeProvision()
});
else if (provision) provisionView = h6(ProvisionView, {
provision,
busy,
onRetry: () => void startProvisioning(true),
onClose: () => void closeProvision()
});
const botList = model.bots.length > 0 ? h6(
"section",
{ className: "dim-listSection" },
h6(
"div",
{ className: "ddt-listHeading dim-listHeading" },
h6("h3", null, "\u5DF2\u7ED1\u5B9A\u7684 QQ \u673A\u5668\u4EBA")
),
h6("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h6("li", { key: account.botId }, h6(AccountCard2, {
account,
busy: busyByBot[account.botId],
removing: removeTarget === account.botId,
onReconnect: () => void botAction(account, "reconnect", QQ_ENDPOINTS.reconnectBot, { botId: account.botId }),
onRequestRemove: () => setRemoveTarget(account.botId),
onCancelRemove: () => setRemoveTarget(null),
onConfirmRemove: async () => {
await botAction(account, "delete", QQ_ENDPOINTS.deleteBot, { botId: account.botId, confirm: true });
if (mounted.current) setRemoveTarget(null);
}
}))))
) : null;
const credentialView = credentialOpen ? h6(CredentialBindingPanel, {
channel: "QQ",
identityLabel: "AppID",
identityPlaceholder: "\u586B\u5199 QQ \u5F00\u653E\u5E73\u53F0 AppID",
secretLabel: "AppSecret",
secretPlaceholder: "\u586B\u5199 QQ \u5F00\u653E\u5E73\u53F0 AppSecret",
busy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : null;
return h6(
"section",
{ className: "ddt-page dqq-page dim-channelPage", "aria-label": "QQ \u8BBE\u7F6E" },
h6(Heading3, {
totals: model.totals,
adding: Boolean(provision),
busy,
onAdd: () => void startProvisioning(),
onCredential: () => {
setCredentialOpen((value) => !value);
setCredentialError(null);
},
credentialOpen,
addButtonRef
}),
model.phase === "loading" ? h6(LoadingView3) : model.phase === "error" ? h6("div", { className: "ddt-card dim-surfaceCard" }, h6("div", { className: "ddt-inlineError dim-inlineError" }, h6("h3", null, "\u65E0\u6CD5\u8BFB\u53D6 QQ \u673A\u5668\u4EBA\u72B6\u6001"), h6("p", null, model.error?.message), h6(Button7, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h6(
React7.Fragment,
null,
credentialView,
provisionView,
model.bots.length === 0 && !provision && !credentialOpen ? h6(EmptyView3, { busy, onStart: () => void startProvisioning() }) : null,
botList
)
);
}
// plugin-src/client/channels/telegram/api.js
var TELEGRAM_RPC_CHANNEL = "/telegram";
var TELEGRAM_ENDPOINTS = TOKEN_BOT_ENDPOINTS;
var api2 = createTokenChannelApi("Telegram", " Bot API \u957F\u8F6E\u8BE2");
var unwrapRpcResult5 = api2.unwrapRpcResult;
var normalizeSnapshot4 = api2.normalizeSnapshot;
var presentError5 = api2.presentError;
// plugin-src/client/channels/telegram/styles.js
var TELEGRAM_STYLE_ID = "xmanrui-dsh-im-telegram-settings";
var CSS5 = String.raw`
.dtg-page { --ddt-accent: #229ed9; --ddt-accent-deep: #1687bd; --ddt-accent-wash: #eaf7fd; }
.dtg-avatar { color: #fff; background: #229ed9; }
.dtg-avatar svg { display: block; }
`;
function installTelegramStyles() {
if (typeof document === "undefined") return () => {
};
const existing = document.querySelector(`style[data-plugin-css="${TELEGRAM_STYLE_ID}"]`);
if (existing) return () => {
};
const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im";
style.dataset.pluginCss = TELEGRAM_STYLE_ID;
style.textContent = CSS5;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/telegram/index.js
var channel2 = createTokenChannelSettings({
channel: "Telegram",
endpoints: TELEGRAM_ENDPOINTS,
api: api2,
LogoGlyph: TelegramLogoGlyph,
installStyles: installTelegramStyles,
pageClass: "dtg-page",
avatarClass: "dtg-avatar",
connectionLabel: "Bot API \u957F\u8F6E\u8BE2",
tokenPlaceholder: "\u586B\u5199 @BotFather \u751F\u6210\u7684 Bot Token",
emptyTitle: "\u63A5\u5165 Telegram \u673A\u5668\u4EBA",
emptyDescription: "\u5148\u901A\u8FC7 @BotFather \u83B7\u53D6 Bot Token\uFF0C\u518D\u5728\u8FD9\u91CC\u5B8C\u6210\u63A5\u5165\u3002",
platformLabel: "Telegram"
});
var TelegramSettingsTab = channel2.SettingsTab;
var TelegramAccountCard = channel2.AccountCard;
// plugin-src/client/channels/wecom/api.js
var WECOM_RPC_CHANNEL = "/wecom";
var WECOM_ENDPOINTS = Object.freeze({
status: "connection.status",
beginProvisioning: "provision.begin",
pollProvisioning: "provision.poll",
cancelProvisioning: "provision.cancel",
bindCredentials: "bot.bind-credentials",
reconnectBot: "bot.reconnect",
deleteBot: "bot.delete"
});
var PROVISION_STATES3 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
var ACCOUNT_STATES4 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
var QR_DATA_URL3 = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
function isRecord5(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function text3(value, fallback, max = 240) {
return typeof value === "string" && value.trim() ? value.trim().slice(0, max) : fallback;
}
function id3(value) {
const result = text3(value, "", 128);
return /^[a-z\d_-]+$/i.test(result) ? result : void 0;
}
function timestamp4(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
return Number.isNaN(parsed) ? void 0 : parsed;
}
function unwrapRpcResult6(result) {
if (!isRecord5(result) || typeof result.ok !== "boolean") throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
if (!result.ok) {
const error = new Error(text3(result.error?.message, "\u4F01\u4E1A\u5FAE\u4FE1\u64CD\u4F5C\u5931\u8D25"));
error.code = text3(result.error?.code, "WECOM_RPC_ERROR", 80);
throw error;
}
return result.value;
}
function safeQrSource4(value) {
return typeof value === "string" && value.length <= 2 * 1024 * 1024 && QR_DATA_URL3.test(value) ? value : void 0;
}
function normalizeProvisioning4(value, now = Date.now()) {
const source = isRecord5(value?.provisioning) ? value.provisioning : value;
if (!isRecord5(source)) throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u7ED1\u5B9A\u8FDB\u5EA6");
const attemptId = id3(source.attemptId);
if (!attemptId) throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1");
const reported = text3(source.status, "failed", 32);
const result = {
attemptId,
status: PROVISION_STATES3.has(reported) ? reported : "failed",
expiresAt: timestamp4(source.expiresAt) ?? now + 5 * 6e4,
pollIntervalMs: Math.min(1e4, Math.max(500, Number(source.pollIntervalMs) || 1e3)),
qrRevision: Number.isSafeInteger(source.qrRevision) ? source.qrRevision : 0
};
const qrCodeDataUrl = safeQrSource4(source.qrCodeDataUrl);
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
if (id3(source.botId)) result.botId = id3(source.botId);
if (isRecord5(source.error)) result.error = {
code: text3(source.error.code, "WECOM_PROVISION_FAILED", 80),
message: text3(source.error.message, "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210")
};
return result;
}
function normalizeBot4(value) {
if (!isRecord5(value) || !id3(value.botId)) return void 0;
const connected = value.connected === true;
const state = ACCOUNT_STATES4.has(value.state) ? value.state : "offline";
return {
botId: id3(value.botId),
connected,
state: connected ? "connected" : state,
bot: {
name: text3(value.bot?.name, "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA", 100),
appIdMasked: text3(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
},
health: {
summary: text3(value.health?.summary, connected ? "\u4F01\u4E1A\u5FAE\u4FE1 WebSocket \u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"),
lastCheckedAt: timestamp4(value.health?.lastCheckedAt)
},
error: isRecord5(value.error) ? {
code: text3(value.error.code, "WECOM_ACCOUNT_ERROR", 80),
message: text3(value.error.message, "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA")
} : null
};
}
function normalizeSnapshot5(value) {
const source = isRecord5(value?.snapshot) ? value.snapshot : value;
if (!isRecord5(source) || !Array.isArray(source.bots)) throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
const bots = source.bots.map(normalizeBot4).filter(Boolean);
return {
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
bots,
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
provisioning: source.provisioning ? normalizeProvisioning4(source.provisioning) : null
};
}
function presentError6(error) {
return {
code: text3(error?.code, "WECOM_ERROR", 80),
message: text3(error?.message, "\u4F01\u4E1A\u5FAE\u4FE1\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")
};
}
function formatRemaining4(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/wecom/index.js
var React8 = __toESM(require("react"), 1);
// plugin-src/client/channels/wecom/styles.js
var WECOM_STYLE_ID = "xmanrui-dsh-im-wecom-settings";
var CSS6 = String.raw`
.dwecom-page { --ddt-accent: #3370ff; --ddt-accent-deep: #245bdb; --ddt-accent-wash: #eef4ff; }
.dwecom-avatar, .dwecom-brand { color: #3370ff; background: #fff; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); }
.dwecom-avatar svg, .dwecom-brand svg { display: block; }
`;
function installWecomStyles() {
if (typeof document === "undefined") return () => {
};
const existing = document.querySelector(`style[data-plugin-css="${WECOM_STYLE_ID}"]`);
if (existing) return () => {
};
const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im";
style.dataset.pluginCss = WECOM_STYLE_ID;
style.textContent = CSS6;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/wecom/index.js
var h7 = React8.createElement;
var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
var Button9 = React8.forwardRef(function Button10({ children, kind = "secondary", className = "", ...props }, ref) {
return h7("button", {
...props,
ref,
type: "button",
className: `ddt-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function checkedTime4(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 Heading4({ totals, adding, busy, onAdd, onCredential, credentialOpen, addButtonRef }) {
return h7(
"div",
{ className: "ddt-heading" },
h7(
"div",
{ className: "ddt-tools" },
h7(
"div",
{ className: "dim-bindActions" },
h7(Button9, {
kind: "primary",
className: "dim-scanButton",
onClick: onAdd,
disabled: adding || busy,
ref: addButtonRef,
"aria-label": "\u626B\u7801\u63A5\u5165\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA"
}, h7(QrActionIcon), adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA"),
h7(Button9, {
kind: "credential",
className: "dim-credentialButton",
onClick: onCredential,
disabled: adding || busy,
"aria-pressed": credentialOpen,
"aria-label": "\u4F7F\u7528 Bot ID \u548C Secret \u7ED1\u5B9A\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA"
}, h7(CredentialActionIcon), credentialOpen ? "\u6536\u8D77\u51ED\u636E" : "\u624B\u52A8\u63A5\u5165")
),
totals.configured > 0 ? h7(
"div",
{ className: "ddt-badge dim-onlineBadge" },
h7("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)
) : null
)
);
}
function LoadingView4() {
return h7(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h7("div", { className: "ddt-spinner dim-spinner" }),
h7("span", null, "\u6B63\u5728\u8BFB\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u72B6\u6001\u2026")
);
}
function EmptyView4({ busy, onStart }) {
return h7(
"div",
{ className: "ddt-card dim-surfaceCard" },
h7(
"div",
{ className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" },
h7(
"div",
{ className: "dim-emptyCopy" },
h7(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h7("span", { className: "ddt-dot dim-stateDot" }),
h7("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA")
),
h7("h3", null, "\u4F7F\u7528\u4F01\u4E1A\u5FAE\u4FE1 App \u626B\u7801\u521B\u5EFA\u667A\u80FD\u673A\u5668\u4EBA"),
h7("p", null, "\u626B\u7801\u7531\u817E\u8BAF\u5B98\u65B9\u9875\u9762\u5B8C\u6210\uFF0C\u4E0D\u9700\u8981\u624B\u52A8\u586B\u5199 Bot ID \u6216 Secret\u3002\u521B\u5EFA\u6210\u529F\u540E\uFF0C\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u8FDE\u63A5 DeepSeek Harness\u3002"),
h7(
"div",
{ className: "ddt-actions dim-viewActions" },
h7(
Button9,
{ kind: "primary", onClick: onStart, disabled: busy },
busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u4F01\u4E1A\u5FAE\u4FE1\u4E8C\u7EF4\u7801"
)
)
),
h7(
"div",
{ className: "ddt-brandMark dim-emptyBrand dwecom-brand", "aria-hidden": "true" },
h7(WecomLogoGlyph, { size: 64 })
)
)
);
}
function QrPanel3({ provision, now, busy, onRefresh, onCancel }) {
const source = safeQrSource4(provision.qrCodeDataUrl);
const remaining = Math.max(0, provision.expiresAt - now);
const duration = Math.max(1, provision.durationMs ?? 5 * 6e4);
const progress = Math.round(Math.min(1, remaining / duration) * 100);
const refreshing = provision.status === "refreshing";
return h7(
"div",
{ className: "ddt-card dim-surfaceCard" },
h7(
"div",
{ className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" },
h7(
"div",
{ className: "ddt-qrColumn dim-qrColumn" },
h7(
"div",
{ className: "ddt-qrFrame dim-qrFrame" },
source ? h7("img", { src: source, alt: "\u7528\u4E8E\u7ED1\u5B9A\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801" }) : h7(
"div",
{ className: "ddt-qrFallback dim-qrFallback" },
refreshing ? "\u4E8C\u7EF4\u7801\u6B63\u5728\u81EA\u52A8\u5237\u65B0\u2026" : "\u4E8C\u7EF4\u7801\u56FE\u7247\u6B63\u5728\u751F\u6210\u2026"
)
),
h7(
"div",
{ className: "ddt-countdown dim-countdown" },
h7(
"div",
{ className: "ddt-countdownTop dim-countdownTop" },
h7("span", null, "\u5F53\u524D\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"),
h7("strong", null, refreshing ? "--:--" : formatRemaining4(remaining))
),
h7("div", { className: "ddt-progress dim-progress", style: { "--ddt-progress": `${progress}%` } }, h7("span"))
)
),
h7(
"div",
{ className: "ddt-qrCopy dim-qrCopy" },
h7(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h7("span", { className: "ddt-dot dim-stateDot", "data-tone": "warning" }),
h7("span", null, refreshing ? "\u6B63\u5728\u5237\u65B0\u4E8C\u7EF4\u7801" : "\u7B49\u5F85\u4F01\u4E1A\u5FAE\u4FE1 App \u626B\u7801")
),
h7("h3", null, "\u4F7F\u7528\u4F01\u4E1A\u5FAE\u4FE1 App \u5B8C\u6210\u667A\u80FD\u673A\u5668\u4EBA\u6388\u6743"),
h7("p", null, "\u4F01\u4E1A\u5FAE\u4FE1\u5B98\u65B9\u9875\u9762\u4F1A\u521B\u5EFA\u4E00\u4E2A\u667A\u80FD\u673A\u5668\u4EBA\uFF0C\u5E76\u628A\u8FDE\u63A5\u51ED\u636E\u5B89\u5168\u4EA4\u7ED9\u672C\u673A Harness Host\u3002"),
h7(
"ol",
{ className: "ddt-steps dim-steps" },
h7("li", null, "\u6253\u5F00\u4F01\u4E1A\u5FAE\u4FE1 App\uFF0C\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"),
h7("li", null, "\u5728\u817E\u8BAF\u6388\u6743\u9875\u9762\u786E\u8BA4\u521B\u5EFA\u667A\u80FD\u673A\u5668\u4EBA"),
h7("li", null, "\u8FD4\u56DE\u8FD9\u91CC\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210")
),
h7(
"div",
{ className: "ddt-actions dim-viewActions" },
h7(Button9, { onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h7(Button9, { kind: "quiet", onClick: onCancel, disabled: busy }, "\u53D6\u6D88")
)
)
)
);
}
function ProvisionView2({ provision, busy, onRetry, onClose }) {
if (provision.status === "connecting") {
return h7(
"div",
{ className: "ddt-card ddt-loading dim-surfaceCard dim-specialView", "aria-busy": "true" },
h7("div", { className: "ddt-spinner dim-spinner" }),
h7("h3", null, "\u4F01\u4E1A\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u6B63\u5728\u8FDE\u63A5\u673A\u5668\u4EBA"),
h7("p", null, "\u51ED\u636E\u6B63\u5728\u5199\u5165\u672C\u673A\uFF0C\u5E76\u542F\u52A8\u4F01\u4E1A\u5FAE\u4FE1 WebSocket \u6D88\u606F\u8FDE\u63A5\u3002")
);
}
const error = provision.error ?? { code: "WECOM_PROVISION_FAILED", message: "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210" };
return h7(
"div",
{ className: "ddt-card dim-surfaceCard" },
h7(
"div",
{ className: "ddt-inlineError dim-inlineError", role: "alert" },
h7("h3", null, "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"),
h7("p", null, error.message),
h7("span", { className: "ddt-errorCode" }, error.code),
h7(
"div",
{ className: "ddt-actions dim-viewActions" },
h7(Button9, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h7(Button9, { onClick: onClose, disabled: busy }, "\u5173\u95ED")
)
)
);
}
function RemoveConfirmation4({ account, busy, onConfirm, onCancel }) {
return h7(
"div",
{ className: "ddt-confirm dim-confirm", role: "alertdialog" },
h7("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`),
h7("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\u4F01\u4E1A\u5FAE\u4FE1\u5E73\u53F0\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002"),
h7(
"div",
{ className: "ddt-actions dim-viewActions" },
h7(Button9, { onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h7(Button9, { kind: "danger", onClick: onConfirm, disabled: busy }, busy ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165")
)
);
}
function AccountCard3({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) {
const tone = account.connected ? "success" : account.state === "error" ? "error" : "warning";
const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : account.state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
return h7(
"article",
{ className: "ddt-card dim-botCard", "data-bot-id": account.botId },
h7(
"div",
{ className: "ddt-cardBody dim-botCardBody" },
h7(
"div",
{ className: "ddt-accountTop dim-botCardTop" },
h7(
"div",
{ className: "ddt-accountIdentity dim-botIdentity" },
h7("div", { className: "ddt-avatar dim-botAvatar dwecom-avatar", "aria-hidden": "true" }, h7(WecomLogoGlyph, { size: 29 })),
h7(
"div",
{ className: "dim-botName" },
h7("h3", null, account.bot.name),
h7("p", null, account.bot.appIdMasked)
)
),
h7(
"div",
{ className: "ddt-health dim-botHealth" },
h7("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }),
h7("span", null, stateLabel)
)
),
h7(
"dl",
{ className: "ddt-metrics dim-botMetrics" },
h7("div", { className: "ddt-metric dim-botMetric" }, h7("dt", null, "\u6D88\u606F\u901A\u9053"), h7("dd", null, account.connected ? "WebSocket \u957F\u8FDE\u63A5" : "\u79BB\u7EBF")),
h7("div", { className: "ddt-metric dim-botMetric" }, h7("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h7("dd", null, checkedTime4(account.health.lastCheckedAt)))
),
h7(
"div",
{ className: "ddt-accountFooter dim-cardFooter" },
summary ? h7("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
h7(
"div",
{ className: "ddt-actions dim-cardActions" },
h7(Button9, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
h7(Button9, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
)
)
),
removing ? h7(RemoveConfirmation4, {
account,
busy: busy === "delete",
onConfirm: onConfirmRemove,
onCancel: onCancelRemove
}) : null
);
}
function WecomSettingsTab({ rpcCall }) {
const [model, setModel] = React8.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
const [provision, setProvision] = React8.useState(null);
const [busy, setBusy] = React8.useState(false);
const [busyByBot, setBusyByBot] = React8.useState({});
const [removeTarget, setRemoveTarget] = React8.useState(null);
const [credentialOpen, setCredentialOpen] = React8.useState(false);
const [credentialError, setCredentialError] = React8.useState(null);
const [now, setNow] = React8.useState(Date.now());
const mounted = React8.useRef(true);
const addButtonRef = React8.useRef(null);
React8.useEffect(() => {
const disposeDingtalk = installDingtalkStyles();
const disposeWecom = installWecomStyles();
mounted.current = true;
return () => {
mounted.current = false;
disposeWecom();
disposeDingtalk();
};
}, []);
const invoke = React8.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new TypeError("\u4F01\u4E1A\u5FAE\u4FE1\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
return unwrapRpcResult6(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React8.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
try {
const snapshot = normalizeSnapshot5(await invoke(WECOM_ENDPOINTS.status, {}, signal));
if (!mounted.current || signal?.aborted) return void 0;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
if (restore && snapshot.provisioning) setProvision({
...snapshot.provisioning,
durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
});
return snapshot;
} catch (error) {
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
setModel((current) => ({ ...current, phase: silent ? current.phase : "error", error: presentError6(error) }));
}
return void 0;
}
}, [invoke]);
React8.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restore: true });
return () => controller.abort();
}, [loadStatus]);
React8.useEffect(() => {
if (model.phase !== "ready") return void 0;
const controller = new AbortController();
const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
return () => {
controller.abort();
window.clearInterval(timer);
};
}, [loadStatus, model.phase]);
React8.useEffect(() => {
if (!provision || !ACTIVE_STATES2.has(provision.status)) return void 0;
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
return () => window.clearInterval(timer);
}, [provision?.attemptId, provision?.status]);
const startProvisioning = React8.useCallback(async (replace = false) => {
setCredentialOpen(false);
setCredentialError(null);
setBusy(true);
try {
if (replace && provision?.attemptId) await invoke(WECOM_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
if (!mounted.current) return;
setProvision({ status: "starting" });
const started = normalizeProvisioning4(await invoke(WECOM_ENDPOINTS.beginProvisioning, { locale: "zh-CN" }));
if (!mounted.current) return;
setNow(Date.now());
setProvision({ ...started, durationMs: Math.max(1, started.expiresAt - Date.now()) });
} catch (error) {
if (mounted.current) setProvision({ status: "failed", error: presentError6(error) });
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId]);
const bindCredentials = React8.useCallback(async ({ identity, secret }) => {
setBusy(true);
setCredentialError(null);
try {
const snapshot = normalizeSnapshot5(await invoke(
WECOM_ENDPOINTS.bindCredentials,
{ botId: identity, secret }
));
if (!mounted.current) return;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
setCredentialOpen(false);
} catch (error) {
if (mounted.current) setCredentialError(presentError6(error));
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke]);
const closeProvision = React8.useCallback(async () => {
setBusy(true);
try {
if (provision?.attemptId && ACTIVE_STATES2.has(provision.status)) {
await invoke(WECOM_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
}
if (mounted.current) setProvision(null);
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId, provision?.status]);
React8.useEffect(() => {
const attemptId = provision?.attemptId;
if (!attemptId || !ACTIVE_STATES2.has(provision.status)) return void 0;
const controller = new AbortController();
let disposed = false;
let timer;
const poll = async () => {
try {
const current = normalizeProvisioning4(await invoke(WECOM_ENDPOINTS.pollProvisioning, { attemptId }, controller.signal));
if (disposed || controller.signal.aborted || !mounted.current) return;
if (current.status === "connected") {
setProvision(null);
await loadStatus({ signal: controller.signal, silent: true });
return;
}
setProvision((previous) => previous?.attemptId === attemptId ? { ...previous, ...current, durationMs: current.qrRevision !== previous.qrRevision ? Math.max(1, current.expiresAt - Date.now()) : previous.durationMs } : previous);
if (ACTIVE_STATES2.has(current.status)) timer = window.setTimeout(poll, current.pollIntervalMs);
} catch (error) {
if (!disposed && !controller.signal.aborted && mounted.current) {
setProvision((current) => ({ ...current, status: "failed", error: presentError6(error) }));
}
}
};
timer = window.setTimeout(poll, provision.pollIntervalMs ?? 1e3);
return () => {
disposed = true;
controller.abort();
window.clearTimeout(timer);
};
}, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
const botAction = React8.useCallback(async (account, operation, endpoint, payload) => {
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
try {
const snapshot = normalizeSnapshot5(await invoke(endpoint, payload));
if (mounted.current) setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
return snapshot;
} finally {
if (mounted.current) setBusyByBot((current) => {
const next = { ...current };
delete next[account.botId];
return next;
});
}
}, [invoke]);
let provisionView = null;
if (provision?.status === "starting") provisionView = h7("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h7("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7\u4F01\u4E1A\u5FAE\u4FE1\u4E8C\u7EF4\u7801\u2026");
else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h7(QrPanel3, {
provision,
now,
busy,
onRefresh: () => void startProvisioning(true),
onCancel: () => void closeProvision()
});
else if (provision) provisionView = h7(ProvisionView2, {
provision,
busy,
onRetry: () => void startProvisioning(true),
onClose: () => void closeProvision()
});
const botList = model.bots.length > 0 ? h7(
"section",
{ className: "dim-listSection" },
h7(
"div",
{ className: "ddt-listHeading dim-listHeading" },
h7("h3", null, "\u5DF2\u7ED1\u5B9A\u7684\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA")
),
h7("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h7("li", { key: account.botId }, h7(AccountCard3, {
account,
busy: busyByBot[account.botId],
removing: removeTarget === account.botId,
onReconnect: () => void botAction(account, "reconnect", WECOM_ENDPOINTS.reconnectBot, { botId: account.botId }),
onRequestRemove: () => setRemoveTarget(account.botId),
onCancelRemove: () => setRemoveTarget(null),
onConfirmRemove: async () => {
await botAction(account, "delete", WECOM_ENDPOINTS.deleteBot, { botId: account.botId, confirm: true });
if (mounted.current) setRemoveTarget(null);
}
}))))
) : null;
const credentialView = credentialOpen ? h7(CredentialBindingPanel, {
channel: "\u4F01\u4E1A\u5FAE\u4FE1",
identityLabel: "Bot ID",
identityPlaceholder: "\u586B\u5199\u4F01\u4E1A\u5FAE\u4FE1\u667A\u80FD\u673A\u5668\u4EBA Bot ID",
secretLabel: "Secret",
secretPlaceholder: "\u586B\u5199\u4F01\u4E1A\u5FAE\u4FE1\u667A\u80FD\u673A\u5668\u4EBA Secret",
busy,
error: credentialError,
onSubmit: bindCredentials,
onCancel: () => {
setCredentialOpen(false);
setCredentialError(null);
}
}) : null;
return h7(
"section",
{ className: "ddt-page dwecom-page dim-channelPage", "aria-label": "\u4F01\u4E1A\u5FAE\u4FE1\u8BBE\u7F6E" },
h7(Heading4, {
totals: model.totals,
adding: Boolean(provision),
busy,
onAdd: () => void startProvisioning(),
onCredential: () => {
setCredentialOpen((value) => !value);
setCredentialError(null);
},
credentialOpen,
addButtonRef
}),
model.phase === "loading" ? h7(LoadingView4) : model.phase === "error" ? h7("div", { className: "ddt-card dim-surfaceCard" }, h7("div", { className: "ddt-inlineError dim-inlineError" }, h7("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u72B6\u6001"), h7("p", null, model.error?.message), h7(Button9, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h7(
React8.Fragment,
null,
credentialView,
provisionView,
model.bots.length === 0 && !provision && !credentialOpen ? h7(EmptyView4, { busy, onStart: () => void startProvisioning() }) : null,
botList
)
);
}
// plugin-src/client/channels/weixin/index.js
var React9 = __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_STATES5 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
var PROVISION_STATES4 = /* @__PURE__ */ new Set([
"starting",
"pending",
"scanned",
"needs_verification",
"connecting",
"connected",
"expired",
"failed",
"cancelled"
]);
function isRecord6(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function string(value, fallback = "") {
return typeof value === "string" && value.trim() ? value.trim() : fallback;
}
function timestamp5(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function unwrapRpcResult7(result) {
if (!isRecord6(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 safeQrSource5(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 normalizeProvisioning5(value) {
if (!isRecord6(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_STATES4.has(value.status) ? value.status : "failed";
const result = {
attemptId: string(value.attemptId),
status,
expiresAt: timestamp5(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 = safeQrSource5(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 (isRecord6(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 normalizeBot5(value) {
if (!isRecord6(value) || !string(value.botId) || !isRecord6(value.bot)) return null;
const state = ACCOUNT_STATES5.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: timestamp5(value.health?.lastCheckedAt)
},
stats: {
messagesReceived: Math.max(0, Number(value.stats?.messagesReceived) || 0),
messagesReplied: Math.max(0, Number(value.stats?.messagesReplied) || 0)
},
error: isRecord6(value.error) ? {
code: string(value.error.code, "WEIXIN_ACCOUNT_ERROR"),
message: string(value.error.message, "\u5FAE\u4FE1\u8FDE\u63A5\u672A\u5C31\u7EEA")
} : null
};
}
function normalizeSnapshot6(value) {
if (!isRecord6(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(normalizeBot5).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 ? normalizeProvisioning5(value.provisioning) : null
};
}
function presentError7(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 formatRemaining5(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 CSS7 = 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-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 = CSS7;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/weixin/index.js
var h8 = React9.createElement;
var Button11 = React9.forwardRef(function Button12({ children, kind = "secondary", className = "", ...props }, ref) {
return h8("button", {
...props,
ref,
type: "button",
className: `dxw-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function Heading5({ totals, adding, busy, onAdd, addButtonRef }) {
return h8(
"div",
{ className: "dxw-heading" },
h8(
"div",
{ className: "dxw-tools" },
h8(Button11, {
kind: "primary",
className: "dim-scanButton",
onClick: onAdd,
disabled: adding || busy,
ref: addButtonRef,
"aria-label": "\u626B\u7801\u63A5\u5165\u5FAE\u4FE1\u673A\u5668\u4EBA"
}, h8(QrActionIcon), adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA"),
totals.configured > 0 ? h8(
"div",
{ className: "dxw-badge dim-onlineBadge" },
h8("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)
) : null
)
);
}
function LoadingView5() {
return h8(
"div",
{ className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h8("div", { className: "dxw-spinner dim-spinner" }),
h8("span", null, "\u6B63\u5728\u8BFB\u53D6\u5FAE\u4FE1\u8FDE\u63A5\u72B6\u6001\u2026")
);
}
function EmptyView5({ onStart, busy }) {
return h8(
"div",
{ className: "dxw-card dim-surfaceCard" },
h8(
"div",
{ className: "dxw-cardBody dxw-empty dim-surfaceBody dim-emptyView" },
h8(
"div",
{ className: "dim-emptyCopy" },
h8(
"div",
{ className: "dxw-stateLabel dim-stateLabel" },
h8("span", { className: "dxw-dot dim-stateDot" }),
h8("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1")
),
h8("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u5C31\u80FD\u5728\u5FAE\u4FE1\u91CC\u4F7F\u7528 Harness"),
h8("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"),
h8(
"div",
{ className: "dxw-actions dim-viewActions" },
h8(
Button11,
{ kind: "primary", onClick: onStart, disabled: busy },
busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u5FAE\u4FE1\u4E8C\u7EF4\u7801"
)
)
),
h8("div", { className: "dxw-logo dim-emptyBrand", "aria-hidden": "true" }, h8(WeixinLogoGlyph, { size: 64 }))
)
);
}
function QrPanel4({ provision, now, busy, onRefresh, onCancel }) {
const [imageFailed, setImageFailed] = React9.useState(false);
const source = safeQrSource5(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);
React9.useEffect(() => setImageFailed(false), [source]);
return h8(
"div",
{ className: "dxw-card dim-surfaceCard" },
h8(
"div",
{ className: "dxw-cardBody dxw-qrLayout dim-surfaceBody dim-qrLayout" },
h8(
"div",
{ className: "dxw-qrColumn dim-qrColumn" },
h8(
"div",
{ className: "dxw-qrFrame dim-qrFrame" },
source && !imageFailed ? h8("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)
}) : h8("div", { className: "dxw-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"),
expired ? h8("div", { className: "dxw-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null
),
h8(
"div",
{ className: "dxw-countdown dim-countdown" },
h8("div", { className: "dim-countdownTop" }, h8("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h8("strong", null, formatRemaining5(remaining))),
h8(
"div",
{ className: "dxw-progress dim-progress", "aria-hidden": "true" },
h8("span", { style: { "--dxw-progress": `${progress}%` } })
)
)
),
h8(
"div",
{ className: "dxw-qrCopy dim-qrCopy" },
h8(
"div",
{ className: "dxw-stateLabel dim-stateLabel" },
h8("span", { className: "dxw-dot dim-stateDot", "data-tone": provision.status === "scanned" ? "success" : "warning" }),
h8("span", null, provision.status === "scanned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4" : "\u7B49\u5F85\u5FAE\u4FE1\u626B\u7801")
),
h8("h3", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u4F7F\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801"),
h8("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"),
h8(
"ol",
{ className: "dxw-steps dim-steps" },
h8("li", null, "\u6253\u5F00\u624B\u673A\u5FAE\u4FE1\u5E76\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"),
h8("li", null, "\u5728\u5FAE\u4FE1\u4E2D\u786E\u8BA4\u8FDE\u63A5\u8BE5\u673A\u5668\u4EBA"),
h8("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6D88\u606F\u957F\u8F6E\u8BE2\u53D8\u4E3A\u5728\u7EBF")
),
h8(
"div",
{ className: "dxw-actions dim-viewActions" },
expired ? h8(Button11, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null,
href ? h8("a", {
className: "dxw-button",
href,
target: "_blank",
rel: "noopener noreferrer"
}, "\u6253\u5F00\u5907\u7528\u94FE\u63A5") : null,
!expired ? h8(Button11, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
h8(Button11, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88")
)
)
)
);
}
function VerificationPanel({ provision, busy, onSubmit, onCancel }) {
const [code, setCode] = React9.useState("");
const valid = /^\d{4,8}$/.test(code);
React9.useEffect(() => setCode(""), [provision.attemptId]);
return h8(
"div",
{ className: "dxw-card dim-surfaceCard" },
h8(
"form",
{
className: "dxw-verify dim-specialView",
onSubmit: (event) => {
event.preventDefault();
if (valid && !busy) onSubmit(code);
}
},
h8(
"div",
{ className: "dxw-stateLabel" },
h8("span", { className: "dxw-dot", "data-tone": "warning" }),
h8("span", null, "\u9700\u8981\u914D\u5BF9\u7801")
),
h8("h3", null, "\u8F93\u5165\u624B\u673A\u5FAE\u4FE1\u663E\u793A\u7684\u6570\u5B57"),
h8("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"),
h8(
"div",
{ className: "dxw-codeRow" },
h8("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
}),
h8("button", {
type: "submit",
className: "dxw-button",
"data-kind": "primary",
disabled: !valid || busy
}, busy ? "\u6B63\u5728\u9A8C\u8BC1\u2026" : "\u7EE7\u7EED\u8FDE\u63A5")
),
h8(Button11, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u7ED1\u5B9A")
)
);
}
function ProgressPanel2({ scanned, onCancel, busy }) {
return h8(
"div",
{ className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" },
h8("div", { className: "dxw-spinner dim-spinner" }),
h8("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"),
h8("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 ? h8(
"div",
{ className: "dxw-actions dim-viewActions", style: { justifyContent: "center", marginTop: 14 } },
h8(Button11, { 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 h8(
"div",
{ className: "dxw-card dim-surfaceCard" },
h8(
"div",
{ className: "dxw-error dim-inlineError", role: "alert" },
h8("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u5FAE\u4FE1\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"),
h8("p", null, error.message),
h8("span", { className: "dxw-errorCode" }, error.code),
h8(
"div",
{ className: "dxw-actions dim-viewActions" },
h8(Button11, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h8(Button11, { onClick: onClose, disabled: busy }, "\u5173\u95ED")
)
)
);
}
function checkedTime5(timestamp7) {
if (!timestamp7) return "\u5C1A\u672A\u68C0\u67E5";
try {
return new Intl.DateTimeFormat("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}).format(new Date(timestamp7));
} catch {
return "\u521A\u521A";
}
}
function AccountCard4({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) {
const state = busy === "reconnect" ? "connecting" : account.state;
const tone = account.connected ? "success" : state === "error" ? "error" : "warning";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
return h8(
"article",
{ className: "dxw-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId },
h8(
"div",
{ className: "dxw-cardBody dim-botCardBody" },
h8(
"div",
{ className: "dxw-accountTop dim-botCardTop" },
h8(
"div",
{ className: "dxw-accountIdentity dim-botIdentity" },
h8("div", { className: "dxw-avatar dim-botAvatar", "aria-hidden": "true" }, h8(WeixinLogoGlyph, { size: 27 })),
h8("div", { className: "dim-botName" }, h8("h3", null, account.bot.name), h8("p", null, account.bot.accountIdMasked))
),
h8(
"div",
{ className: "dxw-health dim-botHealth" },
h8("span", { className: "dxw-dot dim-healthDot", "data-tone": tone }),
h8("span", null, account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA")
)
),
h8(
"dl",
{ className: "dxw-metrics dim-botMetrics" },
h8(
"div",
{ className: "dxw-metric dim-botMetric" },
h8("dt", null, "\u6D88\u606F\u901A\u9053"),
h8("dd", null, account.connected ? "iLink \u957F\u8F6E\u8BE2" : "\u79BB\u7EBF")
),
h8(
"div",
{ className: "dxw-metric dim-botMetric" },
h8("dt", null, "\u6700\u8FD1\u68C0\u67E5"),
h8("dd", null, checkedTime5(account.health.lastCheckedAt))
)
),
h8(
"div",
{ className: "dxw-accountFooter dim-cardFooter" },
summary ? h8("div", { className: "dxw-summary dim-cardSummary" }, summary) : null,
h8(
"div",
{ className: "dxw-actions dim-cardActions" },
h8(
Button11,
{ className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) },
busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"
),
h8(Button11, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
)
)
),
removing ? h8(
"div",
{ className: "dxw-confirm dim-confirm", role: "alertdialog" },
h8("strong", null, "\u4ECE\u6B64 Harness \u79FB\u9664\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\uFF1F"),
h8("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"),
h8(
"div",
{ className: "dxw-actions dim-viewActions" },
h8(Button11, { onClick: onCancelRemove, disabled: busy === "delete" }, "\u4FDD\u7559\u8D26\u53F7"),
h8(
Button11,
{ kind: "danger", onClick: onConfirmRemove, disabled: busy === "delete" },
busy === "delete" ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664"
)
)
) : null
);
}
function AccountList2(props) {
return h8(
"section",
{ className: "dim-listSection" },
h8("div", { className: "dxw-listHeading dim-listHeading" }, h8("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7")),
h8("ul", { className: "dxw-list dim-botList" }, props.bots.map((account) => h8(
"li",
{ key: account.botId },
h8(AccountCard4, {
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] = React9.useState({
phase: "loading",
bots: [],
totals: EMPTY_TOTALS3,
revision: 0,
error: null
});
const [provision, setProvision] = React9.useState(null);
const [busy, setBusy] = React9.useState(false);
const [busyByBot, setBusyByBot] = React9.useState({});
const [removeTarget, setRemoveTarget] = React9.useState(null);
const [notice, setNotice] = React9.useState("");
const [now, setNow] = React9.useState(() => Date.now());
const addButtonRef = React9.useRef(null);
const scheduleAnimationFrame = useAnimationFrameScheduler();
const announce = React9.useCallback((value) => {
setNotice("");
scheduleAnimationFrame(() => {
if (value) setNotice(value);
}, "announcement");
}, [scheduleAnimationFrame]);
const invoke = React9.useCallback(async (endpoint, payload = {}, signal) => {
return unwrapRpcResult7(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React9.useCallback(async ({
signal,
silent = false,
restoreProvisioning = false
} = {}) => {
if (!silent) setModel((current) => ({ ...current, phase: "loading", error: null }));
try {
const snapshot = normalizeSnapshot6(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: presentError7(error)
}));
return void 0;
}
}, [invoke]);
React9.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restoreProvisioning: true });
return () => controller.abort();
}, [loadStatus]);
React9.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]);
React9.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 = React9.useCallback(async ({ replace = false } = {}) => {
setBusy(true);
try {
if (replace && provision?.attemptId) {
await invoke(WEIXIN_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
}
setProvision({ status: "starting" });
const started = normalizeProvisioning5(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: presentError7(error),
...provision?.attemptId ? { attemptId: provision.attemptId } : {}
});
} finally {
setBusy(false);
}
}, [announce, invoke, provision?.attemptId]);
const cancelProvisioning = React9.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: presentError7(error) }));
} finally {
setBusy(false);
}
}, [announce, invoke, provision?.attemptId, provision?.status, scheduleAnimationFrame]);
const submitVerification = React9.useCallback(async (verifyCode) => {
if (!provision?.attemptId) return;
setBusy(true);
try {
const next = normalizeProvisioning5(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: presentError7(error) }));
} finally {
setBusy(false);
}
}, [announce, invoke, provision?.attemptId]);
React9.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 = normalizeProvisioning5(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: presentError7(error) } : current);
}
};
scheduler.schedule(poll, provision.pollIntervalMs ?? 1e3);
return () => {
scheduler.dispose();
controller.abort();
};
}, [announce, invoke, loadStatus, provision?.attemptId, provision?.status, provision?.pollIntervalMs]);
const setBotBusy = React9.useCallback((botId, value) => {
setBusyByBot((current) => {
const next = { ...current };
if (value) next[botId] = value;
else delete next[botId];
return next;
});
}, []);
const reconnect = React9.useCallback(async (account) => {
setBotBusy(account.botId, "reconnect");
try {
const snapshot = normalizeSnapshot6(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${presentError7(error).message}`);
} finally {
setBotBusy(account.botId, null);
}
}, [announce, invoke, setBotBusy]);
const remove = React9.useCallback(async (account) => {
setBotBusy(account.botId, "delete");
try {
const snapshot = normalizeSnapshot6(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${presentError7(error).message}`);
} finally {
setBotBusy(account.botId, null);
}
}, [announce, invoke, setBotBusy]);
let provisionView = null;
if (provision?.status === "starting") {
provisionView = h8(ProgressPanel2, { busy });
} else if (["pending", "scanned"].includes(provision?.status)) {
provisionView = h8(QrPanel4, {
provision,
now,
busy,
onRefresh: () => void startProvisioning({ replace: true }),
onCancel: () => void cancelProvisioning()
});
} else if (provision?.status === "needs_verification") {
provisionView = h8(VerificationPanel, {
provision,
busy,
onSubmit: (code) => void submitVerification(code),
onCancel: () => void cancelProvisioning()
});
} else if (provision?.status === "connecting") {
provisionView = h8(ProgressPanel2, {
scanned: true,
busy,
onCancel: () => void cancelProvisioning()
});
} else if (provision && ["failed", "expired", "cancelled"].includes(provision.status)) {
provisionView = h8(ProvisionError3, {
provision,
busy,
onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }),
onClose: () => void cancelProvisioning()
});
}
return h8(
"section",
{ className: "dxw-page dim-channelPage", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" },
h8(Heading5, {
totals: model.totals,
adding: Boolean(provision),
busy,
onAdd: () => void startProvisioning(),
addButtonRef
}),
h8("div", { className: "dxw-visuallyHidden", role: "status", "aria-live": "polite" }, notice),
model.error && model.phase === "ready" ? h8("div", { className: "dxw-statusNotice dim-statusNotice" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null,
model.phase === "loading" ? h8(LoadingView5) : model.phase === "error" ? h8(
"div",
{ className: "dxw-card dim-surfaceCard" },
h8(
"div",
{ className: "dxw-error dim-inlineError" },
h8("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u5FAE\u4FE1\u72B6\u6001"),
h8("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"),
h8(Button11, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
)
) : h8(
React9.Fragment,
null,
provisionView,
model.bots.length === 0 && !provision ? h8(EmptyView5, { onStart: () => void startProvisioning(), busy }) : null,
model.bots.length > 0 ? h8(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/channels/whatsapp/api.js
var WHATSAPP_RPC_CHANNEL = "/whatsapp";
var WHATSAPP_ENDPOINTS = Object.freeze({
status: "connection.status",
beginProvisioning: "provision.begin",
pollProvisioning: "provision.poll",
cancelProvisioning: "provision.cancel",
reconnectBot: "bot.reconnect",
deleteBot: "bot.delete"
});
var PROVISION_STATES5 = /* @__PURE__ */ new Set(["starting", "pending", "connecting", "connected", "failed", "cancelled"]);
var BOT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
var QR_DATA_URL4 = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
function isRecord7(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function text4(value, fallback, max = 240) {
return typeof value === "string" && value.trim() ? value.trim().slice(0, max) : fallback;
}
function id4(value) {
const result = text4(value, "", 128);
return /^[a-z\d_-]+$/i.test(result) ? result : void 0;
}
function timestamp6(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
return Number.isNaN(parsed) ? void 0 : parsed;
}
function unwrapRpcResult8(result) {
if (!isRecord7(result) || typeof result.ok !== "boolean") {
throw new Error("WhatsApp \u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
}
if (!result.ok) {
const error = new Error(text4(result.error?.message, "WhatsApp \u64CD\u4F5C\u5931\u8D25"));
error.code = text4(result.error?.code, "WHATSAPP_RPC_ERROR", 80);
throw error;
}
return result.value;
}
function safeQrSource6(value) {
return typeof value === "string" && value.length <= 2 * 1024 * 1024 && QR_DATA_URL4.test(value) ? value : void 0;
}
function normalizeProvisioning6(value, now = Date.now()) {
const source = isRecord7(value?.provisioning) ? value.provisioning : value;
if (!isRecord7(source)) throw new Error("WhatsApp \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u8FDB\u5EA6");
const attemptId = id4(source.attemptId);
if (!attemptId) throw new Error("WhatsApp \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u626B\u7801\u4EFB\u52A1");
const reported = text4(source.status, "failed", 32);
const result = {
attemptId,
status: PROVISION_STATES5.has(reported) ? reported : "failed",
expiresAt: timestamp6(source.expiresAt) ?? now + 6e4,
pollIntervalMs: Math.min(5e3, Math.max(500, Number(source.pollIntervalMs) || 1e3)),
qrRevision: Number.isSafeInteger(source.qrRevision) ? source.qrRevision : 0
};
const qrCodeDataUrl = safeQrSource6(source.qrCodeDataUrl);
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
if (id4(source.botId)) result.botId = id4(source.botId);
if (isRecord7(source.error)) result.error = {
code: text4(source.error.code, "WHATSAPP_PROVISION_FAILED", 80),
message: text4(source.error.message, "WhatsApp \u6CA1\u6709\u63A5\u5165\u5B8C\u6210")
};
return result;
}
function normalizeBot6(value) {
if (!isRecord7(value) || !id4(value.botId)) return void 0;
const connected = value.connected === true;
const state = BOT_STATES.has(value.state) ? value.state : "offline";
return {
botId: id4(value.botId),
connected,
state: connected ? "connected" : state,
bot: {
name: text4(value.bot?.name, "WhatsApp\u673A\u5668\u4EBA", 100),
idMasked: text4(value.bot?.idMasked, "WhatsApp\u8D26\u53F7", 140)
},
health: {
summary: text4(value.health?.summary, connected ? "WhatsApp Web \u5173\u8054\u8BBE\u5907\u8FD0\u884C\u6B63\u5E38" : "WhatsApp \u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"),
lastCheckedAt: timestamp6(value.health?.lastCheckedAt)
},
error: isRecord7(value.error) ? {
code: text4(value.error.code, "WHATSAPP_ACCOUNT_ERROR", 80),
message: text4(value.error.message, "WhatsApp \u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA")
} : null
};
}
function normalizeSnapshot7(value) {
const source = isRecord7(value?.snapshot) ? value.snapshot : value;
if (!isRecord7(source) || !Array.isArray(source.bots)) {
throw new Error("WhatsApp \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
}
const bots = source.bots.map(normalizeBot6).filter(Boolean);
return {
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
bots,
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
provisioning: source.provisioning ? normalizeProvisioning6(source.provisioning) : null
};
}
function presentError8(error) {
return {
code: text4(error?.code, "WHATSAPP_ERROR", 80),
message: text4(error?.message, "WhatsApp \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")
};
}
function formatRemaining6(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/whatsapp/index.js
var React10 = __toESM(require("react"), 1);
// plugin-src/client/channels/whatsapp/styles.js
var WHATSAPP_STYLE_ID = "xmanrui-dsh-im-whatsapp-settings";
var CSS8 = String.raw`
.dwa-page { --ddt-accent: #25d366; --ddt-accent-deep: #128c7e; --ddt-accent-wash: #eafbf0; }
.dwa-avatar { color: #fff; background: #25d366; }
.dwa-avatar svg { display: block; }
`;
function installWhatsappStyles() {
if (typeof document === "undefined") return () => {
};
const existing = document.querySelector(`style[data-plugin-css="${WHATSAPP_STYLE_ID}"]`);
if (existing) return () => {
};
const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im";
style.dataset.pluginCss = WHATSAPP_STYLE_ID;
style.textContent = CSS8;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/channels/whatsapp/index.js
var h9 = React10.createElement;
var ACTIVE_STATES3 = /* @__PURE__ */ new Set(["pending", "connecting"]);
var Button13 = React10.forwardRef(function Button14({ children, kind = "secondary", className = "", ...props }, ref) {
return h9("button", {
...props,
ref,
type: "button",
className: `ddt-button ${className}`.trim(),
"data-kind": kind
}, children);
});
function checkedTime6(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 Heading6({ totals, busy, onAdd, addButtonRef }) {
return h9(
"div",
{ className: "ddt-heading" },
h9(
"div",
{ className: "ddt-tools" },
h9(
"div",
{ className: "dim-bindActions" },
h9(Button13, {
kind: "primary",
className: "dim-scanButton",
onClick: onAdd,
disabled: busy,
ref: addButtonRef,
"aria-label": "\u626B\u7801\u63A5\u5165 WhatsApp \u673A\u5668\u4EBA"
}, h9(QrActionIcon), busy ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u673A\u5668\u4EBA")
),
totals.configured > 0 ? h9(
"div",
{ className: "ddt-badge dim-onlineBadge" },
h9("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)
) : null
)
);
}
function LoadingView6() {
return h9("div", {
className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView",
"aria-busy": "true"
}, h9("div", { className: "ddt-spinner dim-spinner" }), "\u6B63\u5728\u8BFB\u53D6 WhatsApp \u673A\u5668\u4EBA\u72B6\u6001\u2026");
}
function EmptyView6({ busy, onStart }) {
return h9(
"div",
{ className: "ddt-card dim-surfaceCard" },
h9(
"div",
{ className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" },
h9(
"div",
{ className: "dim-emptyCopy" },
h9(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h9("span", { className: "ddt-dot dim-stateDot" }),
h9("span", null, "\u5C1A\u672A\u63A5\u5165 WhatsApp \u673A\u5668\u4EBA")
),
h9("h3", null, "\u626B\u7801\u7ED1\u5B9A WhatsApp \u673A\u5668\u4EBA"),
h9("p", null, "\u4F7F\u7528\u624B\u673A WhatsApp \u626B\u63CF\u4E8C\u7EF4\u7801\u5373\u53EF\u63A5\u5165\u3002"),
h9(
"div",
{ className: "ddt-actions dim-viewActions" },
h9(
Button13,
{ kind: "primary", onClick: onStart, disabled: busy },
busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u4E8C\u7EF4\u7801"
)
)
),
h9("div", {
className: "ddt-brandMark dim-emptyBrand dwa-avatar",
"aria-hidden": "true"
}, h9(WhatsappLogoGlyph, { size: 64 }))
)
);
}
function QrPanel5({ provision, now, busy, onRefresh, onCancel }) {
const source = safeQrSource6(provision.qrCodeDataUrl);
const remaining = Math.max(0, provision.expiresAt - now);
const duration = Math.max(1, provision.durationMs ?? 6e4);
const progress = Math.round(Math.min(1, remaining / duration) * 100);
return h9(
"div",
{ className: "ddt-card dim-surfaceCard" },
h9(
"div",
{ className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" },
h9(
"div",
{ className: "ddt-qrColumn dim-qrColumn" },
h9(
"div",
{ className: "ddt-qrFrame dim-qrFrame" },
source ? h9("img", {
src: source,
alt: "\u7528\u4E8E\u5173\u8054 WhatsApp \u8BBE\u5907\u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801"
}) : h9("div", { className: "ddt-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u6B63\u5728\u751F\u6210\u2026")
),
h9(
"div",
{ className: "ddt-countdown dim-countdown" },
h9(
"div",
{ className: "ddt-countdownTop dim-countdownTop" },
h9("span", null, "\u5F53\u524D\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"),
h9("strong", null, formatRemaining6(remaining))
),
h9("div", {
className: "ddt-progress dim-progress",
style: { "--ddt-progress": `${progress}%` }
}, h9("span"))
)
),
h9(
"div",
{ className: "ddt-qrCopy dim-qrCopy" },
h9(
"div",
{ className: "ddt-stateLabel dim-stateLabel" },
h9("span", { className: "ddt-dot dim-stateDot", "data-tone": "warning" }),
h9("span", null, "\u7B49\u5F85 WhatsApp \u626B\u7801")
),
h9("h3", null, "\u7528\u624B\u673A WhatsApp \u626B\u63CF\u4E8C\u7EF4\u7801"),
h9(
"ol",
{ className: "ddt-steps dim-steps" },
h9("li", null, "\u6253\u5F00 WhatsApp \u2192 \u8BBE\u7F6E \u2192 \u5DF2\u5173\u8054\u8BBE\u5907"),
h9("li", null, "\u70B9\u51FB\u201C\u5173\u8054\u8BBE\u5907\u201D\u5E76\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801")
),
h9(
"div",
{ className: "ddt-actions dim-viewActions" },
h9(Button13, { onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h9(Button13, { kind: "quiet", onClick: onCancel, disabled: busy }, "\u53D6\u6D88")
)
)
)
);
}
function ProvisionView3({ provision, busy, onRetry, onClose }) {
if (provision.status === "starting" || provision.status === "connecting") {
const starting = provision.status === "starting";
return h9(
"div",
{
className: "ddt-card ddt-loading dim-surfaceCard dim-specialView",
"aria-busy": "true"
},
h9("div", { className: "ddt-spinner dim-spinner" }),
h9("h3", null, starting ? "\u6B63\u5728\u751F\u6210 WhatsApp \u4E8C\u7EF4\u7801" : "\u5DF2\u626B\u7801\uFF0C\u6B63\u5728\u8FDE\u63A5 WhatsApp"),
h9("p", null, starting ? "\u6B63\u5728\u5EFA\u7ACB\u5B89\u5168\u7684\u5173\u8054\u8BBE\u5907\u4F1A\u8BDD\u3002" : "\u5173\u8054\u8BBE\u5907\u6B63\u5728\u63A5\u5165 DeepSeek Harness\u3002")
);
}
const error = provision.error ?? {
code: "WHATSAPP_PROVISION_FAILED",
message: "WhatsApp \u6CA1\u6709\u63A5\u5165\u5B8C\u6210"
};
return h9(
"div",
{ className: "ddt-card dim-surfaceCard" },
h9(
"div",
{ className: "ddt-inlineError dim-inlineError", role: "alert" },
h9("h3", null, "WhatsApp \u6CA1\u6709\u63A5\u5165\u5B8C\u6210"),
h9("p", null, error.message),
h9("span", { className: "ddt-errorCode" }, error.code),
h9(
"div",
{ className: "ddt-actions dim-viewActions" },
h9(Button13, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"),
h9(Button13, { onClick: onClose, disabled: busy }, "\u5173\u95ED")
)
)
);
}
function RemoveConfirmation5({ account, busy, onConfirm, onCancel }) {
return h9(
"div",
{ className: "ddt-confirm dim-confirm", role: "alertdialog" },
h9("strong", null, `\u4ECE DeepSeek Harness \u79FB\u9664\u201C${account.bot.name}\u201D\uFF1F`),
h9("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 WhatsApp \u5173\u8054\u8BBE\u5907\u548C\u4F1A\u8BDD\u6620\u5C04\u3002"),
h9(
"div",
{ className: "ddt-actions dim-viewActions" },
h9(Button13, { onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"),
h9(
Button13,
{ kind: "danger", onClick: onConfirm, disabled: busy },
busy ? "\u6B63\u5728\u79FB\u9664\u2026" : "\u786E\u8BA4\u79FB\u9664\u63A5\u5165"
)
)
);
}
function WhatsappAccountCard({
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";
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
return h9(
"article",
{ className: "ddt-card dim-botCard", "data-bot-id": account.botId },
h9(
"div",
{ className: "ddt-cardBody dim-botCardBody" },
h9(
"div",
{ className: "ddt-accountTop dim-botCardTop" },
h9(
"div",
{ className: "ddt-accountIdentity dim-botIdentity" },
h9("div", {
className: "ddt-avatar dim-botAvatar dwa-avatar",
"aria-hidden": "true"
}, h9(WhatsappLogoGlyph, { size: 29 })),
h9(
"div",
{ className: "dim-botName" },
h9("h3", null, account.bot.name),
h9("p", null, account.bot.idMasked)
)
),
h9(
"div",
{ className: "ddt-health dim-botHealth" },
h9("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }),
h9("span", null, stateLabel)
)
),
h9(
"dl",
{ className: "ddt-metrics dim-botMetrics" },
h9(
"div",
{ className: "ddt-metric dim-botMetric" },
h9("dt", null, "\u6D88\u606F\u901A\u9053"),
h9("dd", null, account.connected ? "WhatsApp Web" : "\u79BB\u7EBF")
),
h9(
"div",
{ className: "ddt-metric dim-botMetric" },
h9("dt", null, "\u6700\u8FD1\u68C0\u67E5"),
h9("dd", null, checkedTime6(account.health.lastCheckedAt))
)
),
h9(
"div",
{ className: "ddt-accountFooter dim-cardFooter" },
summary ? h9("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
h9(
"div",
{ className: "ddt-actions dim-cardActions" },
h9(Button13, {
className: "dim-cardAction",
onClick: onReconnect,
disabled: Boolean(busy)
}, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
h9(Button13, {
className: "dim-cardAction",
kind: "danger",
onClick: onRequestRemove,
disabled: Boolean(busy)
}, "\u79FB\u9664\u63A5\u5165")
)
)
),
removing ? h9(RemoveConfirmation5, {
account,
busy: busy === "delete",
onConfirm: onConfirmRemove,
onCancel: onCancelRemove
}) : null
);
}
function WhatsappSettingsTab({ rpcCall }) {
const [model, setModel] = React10.useState({
phase: "loading",
bots: [],
totals: { configured: 0, connected: 0 },
error: null
});
const [provision, setProvision] = React10.useState(null);
const [busy, setBusy] = React10.useState(false);
const [busyByBot, setBusyByBot] = React10.useState({});
const [removeTarget, setRemoveTarget] = React10.useState(null);
const [now, setNow] = React10.useState(Date.now());
const mounted = React10.useRef(true);
const addButtonRef = React10.useRef(null);
React10.useEffect(() => {
const disposeDingtalk = installDingtalkStyles();
const disposeWhatsapp = installWhatsappStyles();
mounted.current = true;
return () => {
mounted.current = false;
disposeWhatsapp();
disposeDingtalk();
};
}, []);
const invoke = React10.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new TypeError("WhatsApp \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
return unwrapRpcResult8(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React10.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
try {
const snapshot = normalizeSnapshot7(await invoke(WHATSAPP_ENDPOINTS.status, {}, signal));
if (!mounted.current || signal?.aborted) return void 0;
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
if (restore && snapshot.provisioning) setProvision({
...snapshot.provisioning,
durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
});
return snapshot;
} catch (error) {
if (error?.name !== "AbortError" && mounted.current && !signal?.aborted) {
setModel((current) => ({
...current,
phase: silent ? current.phase : "error",
error: presentError8(error)
}));
}
return void 0;
}
}, [invoke]);
React10.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restore: true });
return () => controller.abort();
}, [loadStatus]);
React10.useEffect(() => {
if (model.phase !== "ready") return void 0;
const controller = new AbortController();
const timer = window.setInterval(
() => void loadStatus({ signal: controller.signal, silent: true }),
15e3
);
return () => {
controller.abort();
window.clearInterval(timer);
};
}, [loadStatus, model.phase]);
React10.useEffect(() => {
if (!provision || !ACTIVE_STATES3.has(provision.status)) return void 0;
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
return () => window.clearInterval(timer);
}, [provision?.attemptId, provision?.status]);
const startProvisioning = React10.useCallback(async (replace = false) => {
setBusy(true);
try {
if (replace && provision?.attemptId) {
await invoke(WHATSAPP_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
}
if (!mounted.current) return;
setProvision({ status: "starting" });
const started = normalizeProvisioning6(await invoke(WHATSAPP_ENDPOINTS.beginProvisioning, {}));
if (!mounted.current) return;
setNow(Date.now());
setProvision({ ...started, durationMs: Math.max(1, started.expiresAt - Date.now()) });
} catch (error) {
if (mounted.current) setProvision({ status: "failed", error: presentError8(error) });
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId]);
const closeProvision = React10.useCallback(async () => {
setBusy(true);
try {
if (provision?.attemptId && ACTIVE_STATES3.has(provision.status)) {
await invoke(WHATSAPP_ENDPOINTS.cancelProvisioning, { attemptId: provision.attemptId });
}
if (mounted.current) setProvision(null);
} finally {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId, provision?.status]);
React10.useEffect(() => {
const attemptId = provision?.attemptId;
if (!attemptId || !ACTIVE_STATES3.has(provision.status)) return void 0;
const controller = new AbortController();
let disposed = false;
let timer;
const schedule = (delay) => {
if (disposed || controller.signal.aborted) return;
timer = window.setTimeout(() => void poll(), delay);
};
const poll = async () => {
try {
const current = normalizeProvisioning6(await invoke(
WHATSAPP_ENDPOINTS.pollProvisioning,
{ attemptId },
controller.signal
));
if (disposed || controller.signal.aborted || !mounted.current) return;
if (current.status === "connected") {
setProvision(null);
await loadStatus({ signal: controller.signal, silent: true });
return;
}
setProvision((previous) => ({
...current,
durationMs: previous?.durationMs ?? Math.max(1, current.expiresAt - Date.now())
}));
if (ACTIVE_STATES3.has(current.status)) schedule(current.pollIntervalMs);
} catch (error) {
if (!disposed && !controller.signal.aborted && mounted.current) {
setProvision({ status: "failed", error: presentError8(error) });
}
}
};
schedule(provision.pollIntervalMs ?? 1e3);
return () => {
disposed = true;
controller.abort();
if (timer) window.clearTimeout(timer);
};
}, [invoke, loadStatus, provision?.attemptId, provision?.status]);
const botAction = React10.useCallback(async (account, operation, endpoint, payload) => {
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
try {
const snapshot = normalizeSnapshot7(await invoke(endpoint, payload));
if (mounted.current) {
setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
}
} finally {
if (mounted.current) setBusyByBot((current) => {
const next = { ...current };
delete next[account.botId];
return next;
});
}
}, [invoke]);
const botList = model.bots.length > 0 ? h9(
"section",
{ className: "dim-listSection" },
h9(
"div",
{ className: "ddt-listHeading dim-listHeading" },
h9("h3", null, "\u5DF2\u63A5\u5165\u7684 WhatsApp \u673A\u5668\u4EBA")
),
h9("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h9("li", { key: account.botId }, h9(WhatsappAccountCard, {
account,
busy: busyByBot[account.botId],
removing: removeTarget === account.botId,
onReconnect: () => void botAction(
account,
"reconnect",
WHATSAPP_ENDPOINTS.reconnectBot,
{ botId: account.botId }
),
onRequestRemove: () => setRemoveTarget(account.botId),
onCancelRemove: () => setRemoveTarget(null),
onConfirmRemove: async () => {
await botAction(account, "delete", WHATSAPP_ENDPOINTS.deleteBot, {
botId: account.botId,
confirm: true
});
if (mounted.current) setRemoveTarget(null);
}
}))))
) : null;
return h9(
"section",
{
className: "ddt-page dwa-page dim-channelPage",
"aria-label": "WhatsApp \u8BBE\u7F6E"
},
h9(Heading6, {
totals: model.totals,
busy,
onAdd: () => void startProvisioning(false),
addButtonRef
}),
model.phase === "loading" ? h9(LoadingView6) : model.phase === "error" ? h9(
"div",
{ className: "ddt-card dim-surfaceCard" },
h9(
"div",
{ className: "ddt-inlineError dim-inlineError" },
h9("h3", null, "\u65E0\u6CD5\u8BFB\u53D6 WhatsApp \u673A\u5668\u4EBA\u72B6\u6001"),
h9("p", null, model.error?.message),
h9(Button13, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
)
) : h9(
React10.Fragment,
null,
provision?.status === "pending" ? h9(QrPanel5, {
provision,
now,
busy,
onRefresh: () => void startProvisioning(true),
onCancel: () => void closeProvision()
}) : provision ? h9(ProvisionView3, {
provision,
busy,
onRetry: () => void startProvisioning(true),
onClose: () => void closeProvision()
}) : model.bots.length === 0 ? h9(EmptyView6, { busy, onStart: () => void startProvisioning(false) }) : null,
botList
)
);
}
// plugin-src/client/styles.js
var IM_STYLE_ID = "xmanrui-dsh-im-settings";
var CSS9 = 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: 28px; height: 28px; }
.dim-logoDingtalk { color: white; background: #1677ff; }
.dim-logoDingtalk svg { width: 24px; height: 24px; }
.dim-logoQq { color: white; background: #1677ff; }
.dim-logoQq svg { width: 21px; height: 21px; }
.dim-logoWecom { background: white; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); }
.dim-logoWecom svg { width: 22px; height: 22px; }
.dim-logoTelegram { color: white; background: #229ed9; }
.dim-logoTelegram svg { width: 21px; height: 21px; }
.dim-logoDiscord { color: white; background: #5865f2; }
.dim-logoDiscord svg { width: 21px; height: 21px; }
.dim-logoWhatsapp { color: white; background: #25d366; }
.dim-logoWhatsapp svg { width: 21px; height: 21px; }
.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, .dim-panel .dqq-page, .dim-panel .dwecom-page, .dim-panel .dwa-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 .dim-bindActions { min-width: 0; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; }
.dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton { flex: none; min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 0 10px; 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 .dim-credentialButton { flex: none; min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 0 10px; border: 1px solid #86909c; border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 5%); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
.dim-panel .dim-actionIcon { width: 15px; height: 15px; flex: 0 0 15px; }
.dim-panel .dim-credentialButton:hover:not(:disabled) { border-color: #4e5969; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
.dim-panel .dim-credentialButton[aria-pressed="true"] { border-color: #4e5969; background: var(--dsw-alias-fill-secondary, #f2f3f5); box-shadow: inset 0 0 0 1px rgb(78 89 105 / 8%); }
.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-credentialPanel { display: grid; gap: 18px; padding: 20px; }
.dim-panel .dim-credentialTitle { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; line-height: 1.35; font-weight: 650; }
.dim-panel .dim-credentialForm { min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 12px; }
.dim-panel .dim-credentialFormSingle { grid-template-columns: minmax(0, 1fr); }
.dim-panel .dim-credentialField { min-width: 0; display: grid; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; font-weight: 560; }
.dim-panel .dim-credentialField input { width: 100%; min-width: 0; height: 38px; padding: 0 11px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; transition: border-color .16s ease, box-shadow .16s ease; }
.dim-panel .dim-credentialField input:focus { border-color: #4e5969; box-shadow: 0 0 0 3px rgb(78 89 105 / 10%); }
.dim-panel .dim-credentialField input::placeholder { color: var(--dsw-alias-label-tertiary, #8f959e); font-family: inherit; }
.dim-panel .dim-credentialError, .dim-panel .dim-credentialActions { grid-column: 1 / -1; }
.dim-panel .dim-credentialError { margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.5; }
.dim-panel .dim-credentialActions { margin-top: 0; }
.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-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%); height: auto; 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: 0 0 0 auto; }
.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-credentialForm { grid-template-columns: minmax(0, 1fr); }
.dim-panel .dim-credentialError, .dim-panel .dim-credentialActions { grid-column: auto; }
.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 = CSS9;
document.head.appendChild(style);
return () => style.remove();
}
// plugin-src/client/index.js
var h10 = React11.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" },
{ id: "wecom", label: "\u4F01\u4E1A\u5FAE\u4FE1" },
{ id: "qq", label: "QQ" },
{ id: "telegram", label: "Telegram" },
{ id: "discord", label: "Discord" },
{ id: "whatsapp", label: "WhatsApp" }
]);
function WeixinLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoWeixin", "aria-hidden": "true" },
h10(WeixinLogoGlyph)
);
}
function FeishuLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoFeishu", "aria-hidden": "true" },
h10(FeishuLogoGlyph)
);
}
function DingtalkLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoDingtalk", "aria-hidden": "true" },
h10(DingtalkLogoGlyph)
);
}
function QqLogo() {
return h10("span", { className: "dim-logo dim-logoQq", "aria-hidden": "true" }, h10(QqLogoGlyph));
}
function WecomLogo() {
return h10("span", { className: "dim-logo dim-logoWecom", "aria-hidden": "true" }, h10(WecomLogoGlyph));
}
function TelegramLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoTelegram", "aria-hidden": "true" },
h10(TelegramLogoGlyph)
);
}
function DiscordLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoDiscord", "aria-hidden": "true" },
h10(DiscordLogoGlyph)
);
}
function WhatsappLogo() {
return h10(
"span",
{ className: "dim-logo dim-logoWhatsapp", "aria-hidden": "true" },
h10(WhatsappLogoGlyph)
);
}
function ChannelLogo({ channel: channel3 }) {
if (channel3 === "weixin") return h10(WeixinLogo);
if (channel3 === "feishu") return h10(FeishuLogo);
if (channel3 === "dingtalk") return h10(DingtalkLogo);
if (channel3 === "wecom") return h10(WecomLogo);
if (channel3 === "qq") return h10(QqLogo);
if (channel3 === "telegram") return h10(TelegramLogo);
if (channel3 === "discord") return h10(DiscordLogo);
return h10(WhatsappLogo);
}
function IMSettingsTab({
dingtalkRpcCall,
discordRpcCall,
feishuRpcCall,
qqRpcCall,
telegramRpcCall,
wecomRpcCall,
weixinRpcCall,
whatsappRpcCall
}) {
const [selected, setSelected] = React11.useState("weixin");
const active = CHANNELS.find((channel3) => channel3.id === selected) ?? CHANNELS[0];
return h10(
"section",
{ className: "dim-page", "aria-label": "IM\u673A\u5668\u4EBA\u8BBE\u7F6E" },
h10(
"header",
{ className: "dim-title" },
h10("p", null, "\u628A\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness")
),
h10(
"div",
{ className: "dim-layout" },
h10(
"nav",
{ className: "dim-rail", role: "tablist", "aria-label": "IM \u6E20\u9053" },
CHANNELS.map((channel3) => h10(
"button",
{
key: channel3.id,
type: "button",
role: "tab",
id: `dim-tab-${channel3.id}`,
className: "dim-channel",
"aria-selected": channel3.id === active.id,
"aria-controls": `dim-panel-${channel3.id}`,
onClick: () => setSelected(channel3.id)
},
h10(ChannelLogo, { channel: channel3.id }),
h10(
"span",
{ className: "dim-channelCopy" },
h10("strong", null, channel3.label)
)
))
),
h10("div", { className: "dim-divider", "aria-hidden": "true" }),
h10("main", {
className: "dim-panel",
role: "tabpanel",
id: `dim-panel-${active.id}`,
"aria-labelledby": `dim-tab-${active.id}`
}, active.id === "weixin" ? h10(WeixinSettingsTab, { rpcCall: weixinRpcCall }) : active.id === "feishu" ? h10(FeishuSettingsTab, { rpcCall: feishuRpcCall }) : active.id === "dingtalk" ? h10(DingtalkSettingsTab, { rpcCall: dingtalkRpcCall }) : active.id === "wecom" ? h10(WecomSettingsTab, { rpcCall: wecomRpcCall }) : active.id === "qq" ? h10(QqSettingsTab, { rpcCall: qqRpcCall }) : active.id === "telegram" ? h10(TelegramSettingsTab, { rpcCall: telegramRpcCall }) : active.id === "discord" ? h10(DiscordSettingsTab, { rpcCall: discordRpcCall }) : h10(WhatsappSettingsTab, { rpcCall: whatsappRpcCall }))
)
);
}
function apply(ctx) {
ctx.effect(() => {
const disposers = [
installFeishuStyles(),
installWeixinStyles(),
installWecomStyles(),
installQqStyles(),
installTelegramStyles(),
installDiscordStyles(),
installWhatsappStyles(),
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);
const qqRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(QQ_RPC_CHANNEL, endpoint, payload, signal);
const wecomRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(WECOM_RPC_CHANNEL, endpoint, payload, signal);
const telegramRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(TELEGRAM_RPC_CHANNEL, endpoint, payload, signal);
const discordRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(DISCORD_RPC_CHANNEL, endpoint, payload, signal);
const whatsappRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(WHATSAPP_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,
discordRpcCall,
feishuRpcCall,
qqRpcCall,
telegramRpcCall,
wecomRpcCall,
weixinRpcCall,
whatsappRpcCall
})
}, IMSettingsTab));
}
return module.exports;
}
});