Files
memind/src/components/ChatHeaderMoreMenu.tsx
T
john 9b4a25799f Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 15:19:03 +08:00

114 lines
3.3 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { WechatAccountButton } from './WechatAccountButton';
const HEADER_POPOVER_OPEN_EVENT = 'tkmind:header-popover-open';
const HEADER_POPOVER_ID = 'header-more-menu';
type MenuItem = {
id: string;
label: string;
onClick: () => void;
disabled?: boolean;
danger?: boolean;
};
export function ChatHeaderMoreMenu({
items,
returnTo,
showWechat = false,
}: {
items: MenuItem[];
returnTo?: string;
showWechat?: boolean;
}) {
const [open, setOpen] = useState(false);
const wrapRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const handleClick = (event: MouseEvent) => {
if (wrapRef.current && !wrapRef.current.contains(event.target as Node)) {
setOpen(false);
}
};
const handleKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') setOpen(false);
};
document.addEventListener('click', handleClick);
document.addEventListener('keydown', handleKey);
return () => {
document.removeEventListener('click', handleClick);
document.removeEventListener('keydown', handleKey);
};
}, [open]);
useEffect(() => {
const handleOtherPopoverOpen = (event: Event) => {
const detail = (event as CustomEvent<{ id?: string }>).detail;
if (detail?.id && detail.id !== HEADER_POPOVER_ID) {
setOpen(false);
}
};
window.addEventListener(HEADER_POPOVER_OPEN_EVENT, handleOtherPopoverOpen);
return () => window.removeEventListener(HEADER_POPOVER_OPEN_EVENT, handleOtherPopoverOpen);
}, []);
return (
<div className={`header-more-menu${open ? ' open' : ''}`} ref={wrapRef}>
<button
type="button"
className="header-icon-btn"
aria-label="更多操作"
aria-expanded={open}
aria-haspopup="menu"
onClick={() =>
setOpen((prev) => {
const next = !prev;
if (next) {
window.dispatchEvent(
new CustomEvent(HEADER_POPOVER_OPEN_EVENT, {
detail: { id: HEADER_POPOVER_ID },
}),
);
}
return next;
})
}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<circle cx="12" cy="5" r="1.75" />
<circle cx="12" cy="12" r="1.75" />
<circle cx="12" cy="19" r="1.75" />
</svg>
</button>
{open && (
<div className="header-more-dropdown" role="menu" aria-label="更多操作">
{items.map((item) => (
<button
key={item.id}
type="button"
className={`header-more-item${item.danger ? ' header-more-item-danger' : ''}`}
role="menuitem"
disabled={item.disabled}
onClick={() => {
setOpen(false);
item.onClick();
}}
>
{item.label}
</button>
))}
{showWechat && (
<>
<div className="header-more-divider" role="separator" />
<div className="header-more-wechat">
<WechatAccountButton returnTo={returnTo} onBeforeAction={() => setOpen(false)} />
</div>
</>
)}
</div>
)}
</div>
);
}