diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58b8b58..c46b41b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,20 +8,23 @@ on: permissions: contents: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: release-mac-arm64: runs-on: macos-14 steps: - name: Check out git repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 22.12 + node-version: 24 cache: "npm" - name: Install Dependencies @@ -66,14 +69,14 @@ jobs: steps: - name: Check out git repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 22.12 + node-version: 24 cache: 'npm' - name: Install Dependencies diff --git a/electron/main.ts b/electron/main.ts index 7996fed..d6f971b 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -988,6 +988,17 @@ function registerIpcHandlers() { } }) + ipcMain.handle('log:clear', async () => { + try { + const logPath = join(app.getPath('userData'), 'logs', 'wcdb.log') + await mkdir(dirname(logPath), { recursive: true }) + await writeFile(logPath, '', 'utf8') + return { success: true } + } catch (e) { + return { success: false, error: String(e) } + } + }) + ipcMain.handle('diagnostics:getExportCardLogs', async (_, options?: { limit?: number }) => { return exportCardDiagnosticsService.snapshot(options?.limit) }) diff --git a/electron/preload.ts b/electron/preload.ts index 2f2874c..2dcc561 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -70,6 +70,7 @@ contextBridge.exposeInMainWorld('electronAPI', { log: { getPath: () => ipcRenderer.invoke('log:getPath'), read: () => ipcRenderer.invoke('log:read'), + clear: () => ipcRenderer.invoke('log:clear'), debug: (data: any) => ipcRenderer.send('log:debug', data) }, diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index 3a6741a..0b81fe2 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -359,16 +359,15 @@ class ChatService { // 这种方式更高效,且不占用 JS 线程,并能直接监听 session/message 目录变更 wcdbService.setMonitor((type, json) => { this.handleSessionStatsMonitorChange(type, json) + const windows = BrowserWindow.getAllWindows() // 广播给所有渲染进程窗口 - BrowserWindow.getAllWindows().forEach((win) => { + windows.forEach((win) => { if (!win.isDestroyed()) { win.webContents.send('wcdb-change', { type, json }) } }) }) } - }) - } /** * 预热 media 数据库列表缓存(后台异步执行) diff --git a/electron/services/wcdbCore.ts b/electron/services/wcdbCore.ts index c118a79..a56cf78 100644 --- a/electron/services/wcdbCore.ts +++ b/electron/services/wcdbCore.ts @@ -173,7 +173,6 @@ export class WcdbCore { } } catch {} } - this.connectMonitorPipe(pipePath) return true } catch (e) { @@ -194,8 +193,14 @@ export class WcdbCore { let buffer = '' this.monitorPipeClient.on('data', (data: Buffer) => { - buffer += data.toString('utf8') - const lines = buffer.split('\n') + const rawChunk = data.toString('utf8') + // macOS 侧可能使用 '\0' 或无换行分隔,统一归一化并兜底拆包 + const normalizedChunk = rawChunk + .replace(/\u0000/g, '\n') + .replace(/}\s*{/g, '}\n{') + + buffer += normalizedChunk + const lines = buffer.split(/\r?\n/) buffer = lines.pop() || '' for (const line of lines) { if (line.trim()) { @@ -207,9 +212,23 @@ export class WcdbCore { } } } + + // 兜底:如果没有分隔符但已形成完整 JSON,则直接上报 + const tail = buffer.trim() + if (tail.startsWith('{') && tail.endsWith('}')) { + try { + const parsed = JSON.parse(tail) + this.monitorCallback?.(parsed.action || 'update', tail) + buffer = '' + } catch { + // 不可解析则继续等待下一块数据 + } + } }) - this.monitorPipeClient.on('error', () => {}) + this.monitorPipeClient.on('error', () => { + // 保持静默,与现有错误处理策略一致 + }) this.monitorPipeClient.on('close', () => { this.monitorPipeClient = null diff --git a/electron/services/wcdbService.ts b/electron/services/wcdbService.ts index 6aee8e9..2f5715f 100644 --- a/electron/services/wcdbService.ts +++ b/electron/services/wcdbService.ts @@ -136,7 +136,7 @@ export class WcdbService { */ setMonitor(callback: (type: string, json: string) => void): void { this.monitorListener = callback; - this.callWorker('setMonitor').catch(() => { }); + this.callWorker<{ success?: boolean }>('setMonitor').catch(() => { }); } /** diff --git a/electron/wcdbWorker.ts b/electron/wcdbWorker.ts index 333527a..8a49cad 100644 --- a/electron/wcdbWorker.ts +++ b/electron/wcdbWorker.ts @@ -20,15 +20,17 @@ if (parentPort) { result = { success: true } break case 'setMonitor': - core.setMonitor((type, json) => { + { + const monitorOk = core.setMonitor((type, json) => { parentPort!.postMessage({ id: -1, type: 'monitor', payload: { type, json } }) }) - result = { success: true } + result = { success: monitorOk } break + } case 'testConnection': result = await core.testConnection(payload.dbPath, payload.hexKey, payload.wxid) break diff --git a/src/components/UpdateDialog.scss b/src/components/UpdateDialog.scss index f12a6d8..a1a4e39 100644 --- a/src/components/UpdateDialog.scss +++ b/src/components/UpdateDialog.scss @@ -14,7 +14,7 @@ .update-dialog { width: 680px; - background: #f5f5f5; + background: var(--bg-secondary, #f5f5f5); border-radius: 24px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); overflow: hidden; @@ -25,7 +25,7 @@ /* Top Section (White/Gradient) */ .dialog-header { - background: #ffffff; + background: var(--bg-primary, #ffffff); padding: 40px 20px 30px; display: flex; flex-direction: column; @@ -41,14 +41,14 @@ left: -50px; width: 200px; height: 200px; - background: radial-gradient(circle, rgba(255, 235, 220, 0.4) 0%, rgba(255, 255, 255, 0) 70%); - opacity: 0.8; + background: radial-gradient(circle, rgba(255, 235, 220, 0.15) 0%, rgba(255, 255, 255, 0) 70%); + opacity: 0.5; pointer-events: none; } .version-tag { - background: #f0eee9; - color: #8c7b6e; + background: var(--bg-tertiary, #f0eee9); + color: var(--text-tertiary, #8c7b6e); padding: 4px 16px; border-radius: 12px; font-size: 13px; @@ -60,21 +60,21 @@ h2 { font-size: 32px; font-weight: 800; - color: #333333; + color: var(--text-primary, #333333); margin: 0 0 12px; letter-spacing: -0.5px; } .subtitle { font-size: 15px; - color: #999999; + color: var(--text-secondary, #999999); font-weight: 400; } } /* Content Section (Light Gray) */ .dialog-content { - background: #f2f2f2; + background: var(--bg-tertiary, #f2f2f2); padding: 24px 40px 40px; flex: 1; display: flex; @@ -87,7 +87,7 @@ margin-bottom: 30px; .icon-box { - background: #fbfbfb; // Beige-ish white + background: var(--bg-primary, #fbfbfb); width: 48px; height: 48px; border-radius: 16px; @@ -96,7 +96,7 @@ justify-content: center; margin-right: 20px; flex-shrink: 0; - color: #8c7b6e; + color: var(--text-tertiary, #8c7b6e); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03); svg { @@ -107,27 +107,38 @@ .text-box { flex: 1; - h3 { - font-size: 18px; + h1, h2, h3, h4, h5, h6 { + color: var(--text-primary, #333333); font-weight: 700; - color: #333333; - margin: 0 0 8px; + margin: 16px 0 8px; + + &:first-child { + margin-top: 0; + } + } + + h2 { + font-size: 16px; + } + + h3 { + font-size: 15px; } p { font-size: 14px; - color: #666666; + color: var(--text-secondary, #666666); line-height: 1.6; - margin: 0; + margin: 4px 0; } ul { - margin: 8px 0 0 18px; + margin: 4px 0 0 18px; padding: 0; li { font-size: 14px; - color: #666666; + color: var(--text-secondary, #666666); line-height: 1.6; } } @@ -142,19 +153,19 @@ justify-content: space-between; margin-bottom: 8px; font-size: 12px; - color: #888; + color: var(--text-secondary, #888); font-weight: 500; } .progress-bar-bg { height: 6px; - background: #e0e0e0; + background: var(--border-color, #e0e0e0); border-radius: 3px; overflow: hidden; .progress-bar-fill { height: 100%; - background: #000000; + background: var(--text-primary, #000000); border-radius: 3px; transition: width 0.3s ease; } @@ -164,7 +175,7 @@ text-align: center; margin-top: 12px; font-size: 13px; - color: #666; + color: var(--text-secondary, #666); } } @@ -175,8 +186,8 @@ .btn-ignore { background: transparent; - color: #666666; - border: 1px solid #d0d0d0; + color: var(--text-secondary, #666666); + border: 1px solid var(--border-color, #d0d0d0); padding: 16px 32px; border-radius: 20px; font-size: 16px; @@ -185,9 +196,9 @@ transition: all 0.2s; &:hover { - background: #f5f5f5; - border-color: #999999; - color: #333333; + background: var(--bg-hover, #f5f5f5); + border-color: var(--text-secondary, #999999); + color: var(--text-primary, #333333); } &:active { @@ -196,11 +207,11 @@ } .btn-update { - background: #000000; - color: #ffffff; + background: var(--text-primary, #000000); + color: var(--bg-primary, #ffffff); border: none; padding: 16px 48px; - border-radius: 20px; // Pill shape + border-radius: 20px; font-size: 16px; font-weight: 600; cursor: pointer; @@ -231,7 +242,7 @@ right: 16px; background: rgba(0, 0, 0, 0.05); border: none; - color: #999; + color: var(--text-secondary, #999); cursor: pointer; width: 32px; height: 32px; @@ -244,7 +255,7 @@ &:hover { background: rgba(0, 0, 0, 0.1); - color: #333; + color: var(--text-primary, #333); transform: rotate(90deg); } } diff --git a/src/components/UpdateDialog.tsx b/src/components/UpdateDialog.tsx index bafdb18..0dce27d 100644 --- a/src/components/UpdateDialog.tsx +++ b/src/components/UpdateDialog.tsx @@ -89,7 +89,6 @@ const UpdateDialog: React.FC = ({
-

优化

{updateInfo.releaseNotes ? (
) : ( diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index db39015..ae49e8e 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -897,6 +897,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { } } + const handleClearLog = async () => { + const confirmed = window.confirm('确定清空 wcdb.log 吗?') + if (!confirmed) return + try { + const result = await window.electronAPI.log.clear() + if (!result.success) { + showMessage(result.error || '清空日志失败', false) + return + } + showMessage('日志已清空', true) + } catch (e: any) { + showMessage(`清空日志失败: ${e}`, false) + } + } + const handleClearAnalyticsCache = async () => { if (isClearingCache) return setIsClearingAnalyticsCache(true) @@ -1427,6 +1442,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { +
@@ -2043,7 +2061,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { {isCheckingUpdate ? '检查中...' : '检查更新'} - )} diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 64806fc..efe7735 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -69,6 +69,7 @@ export interface ElectronAPI { log: { getPath: () => Promise read: () => Promise<{ success: boolean; content?: string; error?: string }> + clear: () => Promise<{ success: boolean; error?: string }> debug: (data: any) => void } diagnostics: {