This commit is contained in:
hicccc77
2026-03-14 18:58:55 +08:00
11 changed files with 114 additions and 51 deletions

View File

@@ -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

View File

@@ -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)
})

View File

@@ -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)
},

View File

@@ -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 数据库列表缓存(后台异步执行)

View File

@@ -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

View File

@@ -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(() => { });
}
/**

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -89,7 +89,6 @@ const UpdateDialog: React.FC<UpdateDialogProps> = ({
<Quote size={20} />
</div>
<div className="text-box">
<h3></h3>
{updateInfo.releaseNotes ? (
<div dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }} />
) : (

View File

@@ -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 = {}) {
<button className="btn btn-secondary" onClick={handleCopyLog}>
<Copy size={16} />
</button>
<button className="btn btn-secondary" onClick={handleClearLog}>
<Trash2 size={16} />
</button>
</div>
</div>
</div>
@@ -2043,7 +2061,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
{isCheckingUpdate ? '检查中...' : '检查更新'}
</button>
</div>
)}
</div>

View File

@@ -69,6 +69,7 @@ export interface ElectronAPI {
log: {
getPath: () => Promise<string>
read: () => Promise<{ success: boolean; content?: string; error?: string }>
clear: () => Promise<{ success: boolean; error?: string }>
debug: (data: any) => void
}
diagnostics: {