Files
memind/src/components/WechatAccountButton.tsx
T
john 229805a070 Improve WeChat MP replies and ship MindSpace/H5 production updates.
Add WeChat service account routing with sync acks, connectivity tests, and context isolation; document deploy runbooks; and bundle related MindSpace, voice, Plaza, and server gateway changes for production rollout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 23:06:43 +08:00

203 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import {
getWechatAgentRouteStatus,
getWechatBindingStatus,
resetWechatAgentRoute,
type WechatAgentRouteStatus,
} from '../api/client';
import { buildWechatAuthorizeUrl } from '../utils/wechat';
function formatRouteTime(timestamp: number | null) {
if (!timestamp) return '暂无';
const date = new Date(timestamp);
if (Number.isNaN(date.getTime())) return '暂无';
const pad = (value: number) => String(value).padStart(2, '0');
return `${date.getMonth() + 1}-${date.getDate()} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
export function WechatAccountButton({
returnTo,
onBeforeAction,
}: {
returnTo?: string;
onBeforeAction?: () => void;
}) {
const [bound, setBound] = useState<boolean | null>(null);
const [nickname, setNickname] = useState<string | null>(null);
const [debugOpen, setDebugOpen] = useState(false);
const [route, setRoute] = useState<WechatAgentRouteStatus | null>(null);
const [routeLoading, setRouteLoading] = useState(false);
const [routeError, setRouteError] = useState<string | null>(null);
const [resetting, setResetting] = useState(false);
useEffect(() => {
void getWechatBindingStatus().then((status) => {
if (!status.enabled) {
setBound(null);
return;
}
setBound(status.bound);
setNickname(status.nickname ?? null);
});
}, []);
useEffect(() => {
if (!debugOpen) return;
let cancelled = false;
setRouteLoading(true);
setRouteError(null);
void getWechatAgentRouteStatus()
.then((status) => {
if (cancelled) return;
setRoute(status);
})
.catch((err) => {
if (cancelled) return;
setRouteError(err instanceof Error ? err.message : '获取公众号 Agent 状态失败');
})
.finally(() => {
if (!cancelled) setRouteLoading(false);
});
return () => {
cancelled = true;
};
}, [debugOpen]);
if (bound === null) return null;
if (bound) {
return (
<>
<button
type="button"
className="ghost-btn wechat-account-bound wechat-account-debug-trigger"
title="查看微信绑定与公众号 Agent 调试信息"
onClick={() => {
onBeforeAction?.();
setDebugOpen(true);
}}
>
{nickname ? ` · ${nickname}` : ''}
</button>
{debugOpen && (
<div className="wechat-debug-overlay" onClick={() => setDebugOpen(false)}>
<section
className="wechat-debug-card"
onClick={(event) => event.stopPropagation()}
aria-modal="true"
role="dialog"
aria-label="公众号 Agent 调试"
>
<div className="wechat-debug-head">
<div>
<p className="wechat-debug-eyebrow">Wechat Agent</p>
<h3></h3>
</div>
<button
type="button"
className="wechat-debug-close"
aria-label="关闭"
onClick={() => setDebugOpen(false)}
>
×
</button>
</div>
<div className="wechat-debug-grid">
<div className="wechat-debug-item">
<span></span>
<strong>{nickname ? `已绑定 · ${nickname}` : '已绑定'}</strong>
</div>
<div className="wechat-debug-item">
<span></span>
<strong>
{route?.enabled ? '已启用' : routeLoading ? '读取中…' : '未启用'}
</strong>
</div>
<div className="wechat-debug-item">
<span>OpenID</span>
<code>{route?.openid ?? '暂无'}</code>
</div>
<div className="wechat-debug-item">
<span>Agent Session</span>
<code>{route?.agentSessionId ?? '暂无'}</code>
</div>
<div className="wechat-debug-item">
<span></span>
<strong>{route?.routeStatus ?? '暂无'}</strong>
</div>
<div className="wechat-debug-item">
<span></span>
<strong>{formatRouteTime(route?.updatedAt ?? null)}</strong>
</div>
</div>
{routeLoading && <p className="wechat-debug-hint"> Agent </p>}
{routeError && <p className="wechat-debug-error">{routeError}</p>}
{!routeLoading && !routeError && route && !route.enabled && (
<p className="wechat-debug-hint">
H5
</p>
)}
<div className="wechat-debug-actions">
<button
type="button"
className="ghost-btn"
disabled={routeLoading || resetting || !route?.enabled}
onClick={() => {
setRouteLoading(true);
setRouteError(null);
void getWechatAgentRouteStatus()
.then((status) => setRoute(status))
.catch((err) =>
setRouteError(err instanceof Error ? err.message : '刷新路由状态失败'),
)
.finally(() => setRouteLoading(false));
}}
>
</button>
<button
type="button"
className="ghost-btn"
disabled={routeLoading || resetting || !route?.enabled}
onClick={() => {
setResetting(true);
setRouteError(null);
void resetWechatAgentRoute()
.then((status) => setRoute(status))
.catch((err) =>
setRouteError(err instanceof Error ? err.message : '重建公众号 Agent 路由失败'),
)
.finally(() => setResetting(false));
}}
>
{resetting ? '重建中…' : '一键重建'}
</button>
</div>
</section>
</div>
)}
</>
);
}
return (
<button
type="button"
className="ghost-btn"
onClick={() => {
onBeforeAction?.();
window.location.href = buildWechatAuthorizeUrl({
intent: 'bind',
returnTo: returnTo ?? window.location.pathname,
});
}}
>
</button>
);
}