From d0267132eba7768cf02b4438ba035c705d853d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Wed, 29 Jul 2026 10:55:35 +0800 Subject: [PATCH] fix(desktop): save IM settings without binding fields #1137 --- desktop/src/i18n/locales/en.ts | 1 + desktop/src/i18n/locales/jp.ts | 1 + desktop/src/i18n/locales/kr.ts | 1 + desktop/src/i18n/locales/zh-TW.ts | 1 + desktop/src/i18n/locales/zh.ts | 1 + desktop/src/pages/AdapterSettings.test.tsx | 129 +++++++++++++++++++++ desktop/src/pages/AdapterSettings.tsx | 35 +++--- 7 files changed, 154 insertions(+), 15 deletions(-) diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index f42b80e9..138b758c 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -793,6 +793,7 @@ Row 9, all 8 cells: continuing from straight down, turning left through lower-le 'settings.adapters.waAllowedUsersPlaceholder': 'e.g. 15551234567@s.whatsapp.net', 'settings.adapters.defaultProject': 'Default Project', 'settings.adapters.defaultProjectHint': 'Default working directory for new IM sessions. If empty, the bot uses the current user working directory.', + 'settings.adapters.clearDefaultProject': 'Clear default project', 'settings.adapters.streamingCard': 'Streaming Card Mode', 'settings.adapters.streamingCardDesc': 'Real-time card updates for better experience', 'settings.adapters.serverUrl': 'Server URL', diff --git a/desktop/src/i18n/locales/jp.ts b/desktop/src/i18n/locales/jp.ts index d9e944a4..6ffac65d 100644 --- a/desktop/src/i18n/locales/jp.ts +++ b/desktop/src/i18n/locales/jp.ts @@ -795,6 +795,7 @@ export const jp: Record = { 'settings.adapters.waAllowedUsersPlaceholder': '例: 15551234567@s.whatsapp.net', 'settings.adapters.defaultProject': 'デフォルトプロジェクト', 'settings.adapters.defaultProjectHint': '新しい IM セッションのデフォルト作業ディレクトリ。空欄の場合、ボットは現在のユーザーの作業ディレクトリを使用します。', + 'settings.adapters.clearDefaultProject': 'デフォルトプロジェクトをクリア', 'settings.adapters.streamingCard': 'ストリーミングカードモード', 'settings.adapters.streamingCardDesc': 'より快適な体験のためにカードをリアルタイムで更新します', 'settings.adapters.serverUrl': 'サーバー URL', diff --git a/desktop/src/i18n/locales/kr.ts b/desktop/src/i18n/locales/kr.ts index 740f669b..4d4ec12a 100644 --- a/desktop/src/i18n/locales/kr.ts +++ b/desktop/src/i18n/locales/kr.ts @@ -795,6 +795,7 @@ export const kr: Record = { 'settings.adapters.waAllowedUsersPlaceholder': '예: 15551234567@s.whatsapp.net', 'settings.adapters.defaultProject': '기본 프로젝트', 'settings.adapters.defaultProjectHint': '새 IM 세션의 기본 작업 디렉터리입니다. 비어 있으면 봇은 현재 사용자 작업 디렉터리를 사용합니다.', + 'settings.adapters.clearDefaultProject': '기본 프로젝트 지우기', 'settings.adapters.streamingCard': '스트리밍 카드 모드', 'settings.adapters.streamingCardDesc': '더 나은 환경을 위해 카드를 실시간으로 업데이트합니다', 'settings.adapters.serverUrl': '서버 URL', diff --git a/desktop/src/i18n/locales/zh-TW.ts b/desktop/src/i18n/locales/zh-TW.ts index e90ac5bf..4b5b27ec 100644 --- a/desktop/src/i18n/locales/zh-TW.ts +++ b/desktop/src/i18n/locales/zh-TW.ts @@ -794,6 +794,7 @@ export const zh: Record = { 'settings.adapters.waAllowedUsersPlaceholder': '如 15551234567@s.whatsapp.net', 'settings.adapters.defaultProject': '預設專案', 'settings.adapters.defaultProjectHint': '新 IM 會話的預設工作目錄。留空則使用當前使用者工作目錄。', + 'settings.adapters.clearDefaultProject': '清除預設專案', 'settings.adapters.streamingCard': '流式卡片模式', 'settings.adapters.streamingCardDesc': '實時更新訊息內容,體驗更好', 'settings.adapters.serverUrl': '伺服器地址', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index ea1c22ae..339880f0 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -794,6 +794,7 @@ export const zh: Record = { 'settings.adapters.waAllowedUsersPlaceholder': '如 15551234567@s.whatsapp.net', 'settings.adapters.defaultProject': '默认项目', 'settings.adapters.defaultProjectHint': '新 IM 会话的默认工作目录。留空则使用当前用户工作目录。', + 'settings.adapters.clearDefaultProject': '清空默认项目', 'settings.adapters.streamingCard': '流式卡片模式', 'settings.adapters.streamingCardDesc': '实时更新消息内容,体验更好', 'settings.adapters.serverUrl': '服务器地址', diff --git a/desktop/src/pages/AdapterSettings.test.tsx b/desktop/src/pages/AdapterSettings.test.tsx index 4f6fefd3..803f56b8 100644 --- a/desktop/src/pages/AdapterSettings.test.tsx +++ b/desktop/src/pages/AdapterSettings.test.tsx @@ -89,6 +89,135 @@ describe('AdapterSettings Feishu onboarding', () => { }) }) +describe('AdapterSettings config saving', () => { + it('does not send WeChat binding-owned fields when saving editable settings', async () => { + const updateConfig = vi.fn(async (_patch: Partial) => {}) + renderAdapterSettings( + { + wechat: { + accountId: 'wx-account', + botToken: '****oken', + baseUrl: 'https://ilinkai.weixin.qq.com', + userId: 'wx-user', + allowedUsers: ['wx-allowed'], + pairedUsers: [{ userId: 'wx-user', displayName: 'WeChat User', pairedAt: 1 }], + }, + }, + { updateConfig }, + ) + + fireEvent.click(screen.getByRole('button', { name: 'Save' })) + + await waitFor(() => { + expect(updateConfig).toHaveBeenCalledTimes(1) + }) + const patch = updateConfig.mock.calls[0]![0] + expect(patch).toMatchObject({ + wechat: { + allowedUsers: ['wx-allowed'], + }, + }) + expect(patch.wechat).toEqual({ + allowedUsers: ['wx-allowed'], + }) + }) + + it('does not send WhatsApp binding-owned fields when saving editable settings', async () => { + const updateConfig = vi.fn(async (_patch: Partial) => {}) + renderAdapterSettings( + { + whatsapp: { + accountJid: '15551234567@s.whatsapp.net', + authDir: '/tmp/whatsapp-auth', + allowedUsers: ['15550000000@s.whatsapp.net'], + pairedUsers: [{ + userId: '15551234567@s.whatsapp.net', + displayName: 'WhatsApp User', + pairedAt: 1, + }], + }, + }, + { updateConfig }, + ) + + fireEvent.click(screen.getByRole('button', { name: 'Save' })) + + await waitFor(() => { + expect(updateConfig).toHaveBeenCalledTimes(1) + }) + const patch = updateConfig.mock.calls[0]![0] + expect(patch.whatsapp).toEqual({ + allowedUsers: ['15550000000@s.whatsapp.net'], + }) + }) + + it('submits empty strings when clearing editable configuration', async () => { + const updateConfig = vi.fn(async (_patch: Partial) => {}) + renderAdapterSettings( + { + defaultProjectDir: '/tmp/existing-project', + telegram: { botToken: '****oken' }, + feishu: { + appId: 'cli_existing', + appSecret: '****cret', + encryptKey: '****-key', + verificationToken: '****oken', + }, + dingtalk: { + clientId: 'ding-client', + clientSecret: '****cret', + endpoint: 'https://custom.example.com', + permissionCardTemplateId: 'permission-template', + }, + }, + { updateConfig }, + ) + + fireEvent.click(screen.getByRole('button', { name: 'Clear default project' })) + fireEvent.change(screen.getByLabelText('Bot Token'), { target: { value: '' } }) + + fireEvent.click(screen.getByRole('tab', { name: 'Feishu' })) + fireEvent.change(screen.getByLabelText('App ID'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('App Secret'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('Encrypt Key'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('Verification Token'), { target: { value: '' } }) + + fireEvent.click(screen.getByRole('tab', { name: 'DingTalk' })) + fireEvent.change(screen.getByLabelText('Client ID'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('Client Secret'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('Stream Endpoint'), { target: { value: '' } }) + fireEvent.change(screen.getByLabelText('Permission Card Template ID'), { target: { value: '' } }) + fireEvent.click(screen.getByRole('button', { name: 'Save' })) + + await waitFor(() => { + expect(updateConfig).toHaveBeenCalledTimes(1) + }) + const patch = updateConfig.mock.calls[0]![0] + expect(patch).toMatchObject({ + defaultProjectDir: '', + telegram: { + botToken: '', + allowedUsers: [], + }, + feishu: { + appId: '', + appSecret: '', + encryptKey: '', + verificationToken: '', + allowedUsers: [], + streamingCard: false, + }, + dingtalk: { + clientId: '', + clientSecret: '', + allowedUsers: [], + endpoint: '', + permissionCardTemplateId: '', + }, + }) + }) +}) + describe('AdapterSettings account unbind confirmation', () => { it('confirms before unbinding a WeChat account', async () => { const unbindWechatAccount = vi.fn(async () => {}) diff --git a/desktop/src/pages/AdapterSettings.tsx b/desktop/src/pages/AdapterSettings.tsx index 2aa7762d..a5f316df 100644 --- a/desktop/src/pages/AdapterSettings.tsx +++ b/desktop/src/pages/AdapterSettings.tsx @@ -257,9 +257,9 @@ export function AdapterSettings() { setSaveStatus('idle') setSaveError('') try { - const patch: Record = {} - - if (defaultProjectDir) patch.defaultProjectDir = defaultProjectDir + const patch: Record = { + defaultProjectDir, + } const tgUsers = tgAllowedUsers .split(',') @@ -269,7 +269,7 @@ export function AdapterSettings() { .filter((n) => !isNaN(n)) patch.telegram = { - botToken: tgBotToken || undefined, + botToken: tgBotToken, allowedUsers: tgUsers.length ? tgUsers : [], } @@ -279,10 +279,10 @@ export function AdapterSettings() { .filter(Boolean) patch.feishu = { - appId: fsAppId || undefined, - appSecret: fsAppSecret || undefined, - encryptKey: fsEncryptKey || undefined, - verificationToken: fsVerificationToken || undefined, + appId: fsAppId, + appSecret: fsAppSecret, + encryptKey: fsEncryptKey, + verificationToken: fsVerificationToken, allowedUsers: fsUsers.length ? fsUsers : [], streamingCard: fsStreamingCard, } @@ -293,7 +293,6 @@ export function AdapterSettings() { .filter(Boolean) patch.wechat = { - ...config.wechat, allowedUsers: wcUsers.length ? wcUsers : [], } @@ -303,7 +302,6 @@ export function AdapterSettings() { .filter(Boolean) patch.whatsapp = { - ...config.whatsapp, allowedUsers: waUsers.length ? waUsers : [], } @@ -313,11 +311,11 @@ export function AdapterSettings() { .filter(Boolean) patch.dingtalk = { - clientId: dtClientId || undefined, - clientSecret: dtClientSecret || undefined, + clientId: dtClientId, + clientSecret: dtClientSecret, allowedUsers: dtUsers.length ? dtUsers : [], - endpoint: dtEndpoint || undefined, - permissionCardTemplateId: dtPermissionCardTemplateId || undefined, + endpoint: dtEndpoint, + permissionCardTemplateId: dtPermissionCardTemplateId, } await updateConfig(patch) @@ -602,7 +600,14 @@ export function AdapterSettings() { - +
+ + {defaultProjectDir && ( + + )} +

{t('settings.adapters.defaultProjectHint')}