From 8a0d3b02d9132a5bcbbe445d8e7ecdae7918f599 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=94=B5=E6=91=87=E5=B0=8F=E5=AD=90?= <969409112@qq.com>
Date: Sun, 9 Aug 2026 14:42:03 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E4=BA=A4=E4=BA=92?=
=?UTF-8?q?=E3=80=81=E7=9F=A5=E8=AF=86=E5=BA=93=E7=9B=AE=E5=BD=95=E4=B8=8E?=
=?UTF-8?q?=20Windows=20=E8=BF=90=E8=A1=8C=E5=BA=93=E6=8F=90=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/index.ts | 25 ++++++++-
src/main/services/cache-service.ts | 15 +++++-
src/main/services/chat-service.ts | 6 +++
src/preload/index.d.ts | 7 ++-
src/preload/index.ts | 2 +
.../src/components/DatabaseConnectionPage.tsx | 16 +++++-
.../components/search/AISearchWorkspace.tsx | 7 +++
.../settings/pages/CacheCleanupPage.tsx | 54 +++++++++++++++----
.../src/styles/settings-preferences.scss | 15 ++++++
src/shared/database-key.ts | 10 ++++
src/shared/windows-runtime.ts | 13 +++++
.../ai-search-cache-consent.test.tsx | 50 +++++++++++++++++
tests/component/cache-cleanup.test.tsx | 42 +++++++++++++++
tests/component/database-connection.test.tsx | 12 +++++
tests/integration/preload-contract.test.ts | 2 +
tests/unit/windows-runtime.test.ts | 27 ++++++++++
16 files changed, 283 insertions(+), 20 deletions(-)
create mode 100644 src/shared/windows-runtime.ts
create mode 100644 tests/component/cache-cleanup.test.tsx
create mode 100644 tests/unit/windows-runtime.test.ts
diff --git a/src/main/index.ts b/src/main/index.ts
index 7de6595..ec5ee08 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -97,7 +97,7 @@ import { agentHubService } from './services/agent-hub-service'
import { appLogger } from './app-logger'
import type { AppLogEntry } from '../shared/app-log'
import { appUpdateService } from './services/app-update-service'
-import { clearCache, getCacheSummary } from './services/cache-service'
+import { clearCache, getCacheSummary, openKnowledgeDirectory } from './services/cache-service'
import type { CacheClearScope } from './services/cache-service'
import { configureRecallArchive, RecallArchiveMonitor } from './services/recall-archive-service'
import { VideoAssetService } from './video-asset-service'
@@ -109,6 +109,10 @@ import { VoiceBatchService } from './voice-pipeline/voice-batch-service'
import type { VoiceBatchRequest, VoiceMessageReference } from '../shared/voice-recognition'
import type { AiSearchPipelineRequest } from '../shared/ai-search'
import type { KnowledgeSearchIpcRequest, KnowledgeSearchIpcResult } from '../shared/knowledge'
+import {
+ isWindowsVcRuntimeMissingError,
+ WINDOWS_VC_RUNTIME_ERROR_MESSAGE
+} from '../shared/windows-runtime'
import { KnowledgeSearchService } from './knowledge/knowledge-search-service'
import { AiSearchPipelineService } from './services/ai-search-pipeline-service'
@@ -506,6 +510,13 @@ app.whenReady().then(async () => {
wcdbBootstrapPromise = bootstrapWcdbNativeAsync().then(() => {
console.log('[WCDB4] async bootstrap complete')
})
+ void wcdbBootstrapPromise.catch((error) => {
+ appLogger.write({
+ level: 'error',
+ scope: 'wcdb-bootstrap',
+ message: error instanceof Error ? error.message : String(error)
+ })
+ })
// 设置应用程序用户模型 ID
electronApp.setAppUserModelId('com.wechatexplorer.app')
@@ -529,6 +540,7 @@ app.whenReady().then(async () => {
ipcMain.handle('app-update:download', () => appUpdateService.download())
ipcMain.handle('app-update:install', () => appUpdateService.install())
ipcMain.handle('cache:getSummary', () => getCacheSummary())
+ ipcMain.handle('cache:openKnowledgeDirectory', () => openKnowledgeDirectory())
ipcMain.handle('cache:clear', async (_, scope: CacheClearScope) => {
const allowedScopes: CacheClearScope[] = ['bootstrap', 'electron', 'knowledge', 'all']
if (!allowedScopes.includes(scope)) return getCacheSummary()
@@ -625,7 +637,16 @@ app.whenReady().then(async () => {
return { success: true, monitoring }
} catch (error) {
console.error('Failed to init DB:', error)
- return { success: false, error: error instanceof Error ? error.message : String(error) }
+ const detail = error instanceof Error ? error.message : String(error)
+ if (isWindowsVcRuntimeMissingError(detail, process.platform)) {
+ return {
+ success: false,
+ code: 'VC_RUNTIME_MISSING',
+ error: WINDOWS_VC_RUNTIME_ERROR_MESSAGE,
+ monitoring: false
+ }
+ }
+ return { success: false, error: detail }
} finally {
dbInitInFlight = null
}
diff --git a/src/main/services/cache-service.ts b/src/main/services/cache-service.ts
index 337607a..d8ff7a5 100644
--- a/src/main/services/cache-service.ts
+++ b/src/main/services/cache-service.ts
@@ -1,4 +1,4 @@
-import { app, session } from 'electron'
+import { app, session, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { clearBootstrapCache } from './bootstrap-cache'
@@ -62,7 +62,8 @@ export function getCacheSummary(): CacheSummary {
{
id: 'knowledge',
label: '本地知识库索引',
- description: '为问问微信建立的所有账号本地检索索引。清理后需手动重新建立,不影响微信原始数据。',
+ description:
+ '为问问微信建立的所有账号本地检索索引。清理后需手动重新建立,不影响微信原始数据。',
...knowledge
}
]
@@ -90,3 +91,13 @@ export async function clearCache(
}
return getCacheSummary()
}
+
+export async function openKnowledgeDirectory(): Promise<{ success: boolean; error?: string }> {
+ try {
+ await fs.ensureDir(KNOWLEDGE_CACHE_DIR)
+ const error = await shell.openPath(KNOWLEDGE_CACHE_DIR)
+ return error ? { success: false, error } : { success: true }
+ } catch (error) {
+ return { success: false, error: error instanceof Error ? error.message : String(error) }
+ }
+}
diff --git a/src/main/services/chat-service.ts b/src/main/services/chat-service.ts
index 6b8a511..b7f517d 100644
--- a/src/main/services/chat-service.ts
+++ b/src/main/services/chat-service.ts
@@ -9,6 +9,10 @@ import type {
DatabaseKeyValidationCode,
DatabaseKeyValidationResult
} from '../../shared/database-key'
+import {
+ isWindowsVcRuntimeMissingError,
+ WINDOWS_VC_RUNTIME_ERROR_MESSAGE
+} from '../../shared/windows-runtime'
import { mergeRecallArchiveMessages, recordRecallArchiveMessages } from './recall-archive-service'
import type { ExportImageQuality } from '../../shared/image-quality'
@@ -675,11 +679,13 @@ const DATABASE_KEY_ERROR_MESSAGES: Record = {
ACCOUNT_MISMATCH: '密钥与当前账号不匹配',
ROOT_UNAVAILABLE: '当前数据库目录不可用',
DATABASE_FILE_MISSING: '数据库文件缺失',
+ VC_RUNTIME_MISSING: WINDOWS_VC_RUNTIME_ERROR_MESSAGE,
UNKNOWN_VALIDATION_ERROR: '未知验证错误'
}
function mapConnectionError(detail: string): DatabaseKeyValidationCode {
const normalized = detail.toLowerCase()
+ if (isWindowsVcRuntimeMissingError(detail, process.platform)) return 'VC_RUNTIME_MISSING'
if (normalized.includes('-1005') || normalized.includes('不匹配')) return 'ACCOUNT_MISMATCH'
if (normalized.includes('session.db') || normalized.includes('数据库文件')) {
return 'DATABASE_FILE_MISSING'
diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts
index b0a0b02..2e5eaeb 100644
--- a/src/preload/index.d.ts
+++ b/src/preload/index.d.ts
@@ -10,6 +10,7 @@ import {
} from '../shared/report-history'
import type {
DatabaseKeyEnvironment,
+ DatabaseInitResult,
DatabaseKeyStorageResult,
DatabaseKeyValidationResult,
AccountDiscoveryResult
@@ -141,10 +142,8 @@ declare global {
onAppUpdateState: (callback: (state: AppUpdateState) => void) => () => void
getCacheSummary: () => Promise
clearCache: (scope: 'bootstrap' | 'electron' | 'knowledge' | 'all') => Promise
- initDb: (
- key: string,
- accountRoot: string
- ) => Promise
+ openKnowledgeDirectory: () => Promise<{ success: boolean; error?: string }>
+ initDb: (key: string, accountRoot: string) => Promise
discoverAccounts: (inputPath: string) => Promise
getBootstrapCache: () => Promise<{
self?: { wxid: string; nickname: string; avatar?: string; accountRoot: string }
diff --git a/src/preload/index.ts b/src/preload/index.ts
index a0e87eb..b080f64 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -66,6 +66,8 @@ const api = {
getCacheSummary: (): Promise => ipcRenderer.invoke('cache:getSummary'),
clearCache: (scope: 'bootstrap' | 'electron' | 'knowledge' | 'all'): Promise =>
ipcRenderer.invoke('cache:clear', scope),
+ openKnowledgeDirectory: (): Promise<{ success: boolean; error?: string }> =>
+ ipcRenderer.invoke('cache:openKnowledgeDirectory'),
initDb: (key: string, accountRoot: string) => ipcRenderer.invoke('db:init', key, accountRoot),
discoverAccounts: (inputPath: string): Promise =>
ipcRenderer.invoke('accounts:discover', inputPath),
diff --git a/src/renderer/src/components/DatabaseConnectionPage.tsx b/src/renderer/src/components/DatabaseConnectionPage.tsx
index 5980d1f..fb4aa7e 100644
--- a/src/renderer/src/components/DatabaseConnectionPage.tsx
+++ b/src/renderer/src/components/DatabaseConnectionPage.tsx
@@ -1,5 +1,6 @@
import React from 'react'
import type { DatabaseKeyEnvironment, WechatAccountCandidate } from '../../../shared/database-key'
+import { WINDOWS_VC_RUNTIME_DOWNLOAD_URL } from '../../../shared/windows-runtime'
const GUIDE_URL =
'https://github.com/Wxw-Gu/WechatExplorer/blob/main/docs/user-guide/getting-started.md'
@@ -461,7 +462,12 @@ export function DatabaseConnectionPage({
>
) : (
- 'Windows 已完整支持,不需要关闭 SIP。'
+ <>
+ Windows 需要 Microsoft Visual C++ 2015-2022 x64 运行库。{' '}
+
+ 下载运行库
+
+ >
)}
{showMacKeyFaq && isMac && (
@@ -521,6 +527,14 @@ export function DatabaseConnectionPage({
)}
{status && {status}
}
+ {platform === 'win32' && (
+
+ 无法加载数据库组件时,请安装 Microsoft Visual C++ 2015-2022 x64 运行库。{' '}
+
+ 下载运行库
+
+
+ )}