diff --git a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx index d8a9936..3b0d1bd 100644 --- a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx +++ b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx @@ -9,15 +9,7 @@ import type { PersonalWechatRuntimeProgressEvent, PersonalWechatRuntimeStatus } from '../../../../shared/personal-wechat-runtime' -import { - Button, - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - Switch -} from '../ui' +import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui' import { isMac, isWindows } from '../../utils/runtime-environment' import { PersonalWechatChatComposer, type ChatMessage } from './PersonalWechatChatComposer' import { PersonalWechatSetupGuide } from './PersonalWechatSetupGuide' @@ -89,7 +81,6 @@ function PersonalWechatMacSendDialog({ const [sendError, setSendError] = useState(null) const [voiceDiagnostic, setVoiceDiagnostic] = useState(null) const [voiceDiagnosticOpen, setVoiceDiagnosticOpen] = useState(false) - const [keepOneBotProcess, setKeepOneBotProcess] = useState(false) const requestIdRef = useRef(0) const restoreFocusRef = useRef(null) const closingRef = useRef(false) @@ -155,36 +146,6 @@ function PersonalWechatMacSendDialog({ return () => window.clearInterval(timer) }, [refreshStatus, senderStatus]) - useEffect(() => { - if (!isMac) return undefined - let active = true - const readKeepProcess = window.api.getPersonalWechatKeepOneBotProcess - if (!readKeepProcess) return undefined - void readKeepProcess().then((keep) => { - if (active && typeof keep === 'boolean') setKeepOneBotProcess(keep) - }) - return () => { - active = false - } - }, []) - - const handleKeepOneBotProcessChange = async (keep: boolean): Promise => { - if (!isMac) return - const saveKeepProcess = window.api.setPersonalWechatKeepOneBotProcess - if (!saveKeepProcess) { - setSendError('请重启 TraceMemo 后再使用“保留 OneBot 进程”') - return - } - setKeepOneBotProcess(keep) - try { - const saved = await saveKeepProcess(keep) - if (typeof saved === 'boolean') setKeepOneBotProcess(saved) - } catch (error) { - setKeepOneBotProcess(!keep) - setSendError(error instanceof Error ? error.message : String(error)) - } - } - const handleDownloadRuntime = async (): Promise => { if (runtimeBusy) return setRuntimeBusy(true) @@ -399,34 +360,14 @@ function PersonalWechatMacSendDialog({ )} {setupReady && isMac && ( -
-
- 保留 OneBot 进程 - void handleKeepOneBotProcessChange(checked)} - aria-label="保留 OneBot 进程" - /> -
+
)} {!setupReady && ( -
- {isMac && ( -
- 保留 OneBot 进程 - void handleKeepOneBotProcessChange(checked)} - aria-label="保留 OneBot 进程" - /> -
- )} +
diff --git a/src/renderer/src/features/settings/pages/PersonalWechatSendPage.tsx b/src/renderer/src/features/settings/pages/PersonalWechatSendPage.tsx index a4ea596..24fdf33 100644 --- a/src/renderer/src/features/settings/pages/PersonalWechatSendPage.tsx +++ b/src/renderer/src/features/settings/pages/PersonalWechatSendPage.tsx @@ -15,7 +15,8 @@ import { DialogHeader, DialogTitle, Input, - Skeleton + Skeleton, + Switch } from '../../../components/ui' import { PersonalWechatSetupGuide } from '../../../components/chat/PersonalWechatSetupGuide' import { PersonalWechatSupportedVersionsContent } from '../../../components/chat/PersonalWechatSupportedVersionsContent' @@ -88,6 +89,8 @@ export function PersonalWechatSendPage({ const [windowsPortInput, setWindowsPortInput] = useState('') const [windowsDetectedPort, setWindowsDetectedPort] = useState('') const [windowsEndpointBusy, setWindowsEndpointBusy] = useState(false) + const [keepOneBotProcess, setKeepOneBotProcess] = useState(false) + const [keepProcessBusy, setKeepProcessBusy] = useState(false) const [showWechatVersions, setShowWechatVersions] = useState(false) const runtimeVersionsTriggerRef = useRef(null) const personalWechatRuntimeSupported = isMac && Boolean(runtimeStatus?.supported) @@ -128,6 +131,21 @@ export function PersonalWechatSendPage({ return () => unsubscribe?.() }, [refresh]) + useEffect(() => { + if (!isMac) return undefined + let active = true + const readKeepProcess = window.api.getPersonalWechatKeepOneBotProcess + if (typeof readKeepProcess !== 'function') return undefined + void readKeepProcess() + .then((keep) => { + if (active && typeof keep === 'boolean') setKeepOneBotProcess(keep) + }) + .catch(() => undefined) + return () => { + active = false + } + }, []) + useEffect(() => { if (isWindows || !senderStatus || senderStatus.canSend || senderStatus.state === 'error') { return undefined @@ -219,6 +237,26 @@ export function PersonalWechatSendPage({ } } + const handleKeepOneBotProcessChange = async (keep: boolean): Promise => { + if (!isMac || keepProcessBusy) return + const saveKeepProcess = window.api.setPersonalWechatKeepOneBotProcess + if (typeof saveKeepProcess !== 'function') { + onNotice('请重启 TraceMemo 后再使用“保留 OneBot 进程”') + return + } + setKeepProcessBusy(true) + setKeepOneBotProcess(keep) + try { + const saved = await saveKeepProcess(keep) + if (typeof saved === 'boolean') setKeepOneBotProcess(saved) + } catch (reason) { + setKeepOneBotProcess(!keep) + onNotice(reason instanceof Error ? reason.message : String(reason)) + } finally { + setKeepProcessBusy(false) + } + } + const bindWechat = async (): Promise => { if (binding) return setBinding(true) @@ -578,6 +616,20 @@ export function PersonalWechatSendPage({ />
) : null} +
+
+ 保留 OneBot 进程 +

+ 退出 TraceMemo 后继续保留进程,重新打开时可以复用。 +

+
+ void handleKeepOneBotProcessChange(checked)} + aria-label="保留 OneBot 进程" + /> +

配置状态

diff --git a/tests/component/personal-wechat-send-dialog.test.tsx b/tests/component/personal-wechat-send-dialog.test.tsx index eb90765..6d509da 100644 --- a/tests/component/personal-wechat-send-dialog.test.tsx +++ b/tests/component/personal-wechat-send-dialog.test.tsx @@ -143,6 +143,7 @@ describe('PersonalWechatSendDialog', () => { renderDialog() await startComposer() expect(screen.getByRole('dialog')).toHaveTextContent('文字转语音') + expect(screen.queryByRole('switch', { name: '保留 OneBot 进程' })).not.toBeInTheDocument() expect(screen.queryByText('验证消息能力')).not.toBeInTheDocument() expect(screen.getByRole('button', { name: '生成语音' })).toBeDisabled() }) diff --git a/tests/component/personal-wechat-send-page.test.tsx b/tests/component/personal-wechat-send-page.test.tsx index ec89c8f..efac1db 100644 --- a/tests/component/personal-wechat-send-page.test.tsx +++ b/tests/component/personal-wechat-send-page.test.tsx @@ -19,6 +19,8 @@ const cancelRuntimeDownload = vi.fn() const removeRuntime = vi.fn() const openRuntimeDirectory = vi.fn() const rebindSender = vi.fn() +const getKeepOneBotProcess = vi.fn() +const setKeepOneBotProcess = vi.fn() const senderStatus = { state: 'online' as const, @@ -88,6 +90,8 @@ describe('PersonalWechatSendPage on macOS', () => { }) openRuntimeDirectory.mockReset().mockResolvedValue({ success: true }) rebindSender.mockReset().mockResolvedValue(senderStatus) + getKeepOneBotProcess.mockReset().mockResolvedValue(false) + setKeepOneBotProcess.mockReset().mockImplementation(async (keep: boolean) => keep) vi.spyOn(window, 'confirm').mockReturnValue(true) Object.defineProperty(window, 'api', { configurable: true, @@ -100,7 +104,9 @@ describe('PersonalWechatSendPage on macOS', () => { cancelPersonalWechatRuntimeDownload: cancelRuntimeDownload, removePersonalWechatRuntime: removeRuntime, openPersonalWechatRuntimeDirectory: openRuntimeDirectory, - rebindPersonalWechatSender: rebindSender + rebindPersonalWechatSender: rebindSender, + getPersonalWechatKeepOneBotProcess: getKeepOneBotProcess, + setPersonalWechatKeepOneBotProcess: setKeepOneBotProcess } }) }) @@ -123,6 +129,18 @@ describe('PersonalWechatSendPage on macOS', () => { expect(versionsDialog).toHaveTextContent('4.1.11.53') }) + it('keeps the OneBot process setting on the macOS WeChat send page', async () => { + const user = userEvent.setup() + render() + + const keepProcess = await screen.findByRole('switch', { name: '保留 OneBot 进程' }) + expect(keepProcess).not.toBeChecked() + await user.click(keepProcess) + + await waitFor(() => expect(setKeepOneBotProcess).toHaveBeenCalledWith(true)) + expect(keepProcess).toBeChecked() + }) + it('downloads a missing macOS runtime from the send page', async () => { getRuntimeStatus.mockResolvedValue({ ...readyRuntime, diff --git a/tests/component/text-to-speech-page-windows.test.tsx b/tests/component/text-to-speech-page-windows.test.tsx index 32cd754..3362cb4 100644 --- a/tests/component/text-to-speech-page-windows.test.tsx +++ b/tests/component/text-to-speech-page-windows.test.tsx @@ -191,4 +191,11 @@ describe('PersonalWechatSendPage on Windows', () => { expect(screen.queryByText(/OneBot/i)).not.toBeInTheDocument() expect(screen.queryByText('微信发送组件')).not.toBeInTheDocument() }) + + it('does not show the macOS process setting on the Windows WeChat send page', async () => { + render() + + expect(await screen.findByRole('spinbutton', { name: '微信发送能力端口' })).toBeVisible() + expect(screen.queryByRole('switch', { name: '保留 OneBot 进程' })).not.toBeInTheDocument() + }) })