mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
Merge branch 'dev' of https://github.com/hicccc77/WeFlow into dev
This commit is contained in:
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -8,20 +8,23 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-mac-arm64:
|
release-mac-arm64:
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.12
|
node-version: 24
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
@@ -66,14 +69,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.12
|
node-version: 24
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
|
|||||||
@@ -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 }) => {
|
ipcMain.handle('diagnostics:getExportCardLogs', async (_, options?: { limit?: number }) => {
|
||||||
return exportCardDiagnosticsService.snapshot(options?.limit)
|
return exportCardDiagnosticsService.snapshot(options?.limit)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
log: {
|
log: {
|
||||||
getPath: () => ipcRenderer.invoke('log:getPath'),
|
getPath: () => ipcRenderer.invoke('log:getPath'),
|
||||||
read: () => ipcRenderer.invoke('log:read'),
|
read: () => ipcRenderer.invoke('log:read'),
|
||||||
|
clear: () => ipcRenderer.invoke('log:clear'),
|
||||||
debug: (data: any) => ipcRenderer.send('log:debug', data)
|
debug: (data: any) => ipcRenderer.send('log:debug', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -359,16 +359,15 @@ class ChatService {
|
|||||||
// 这种方式更高效,且不占用 JS 线程,并能直接监听 session/message 目录变更
|
// 这种方式更高效,且不占用 JS 线程,并能直接监听 session/message 目录变更
|
||||||
wcdbService.setMonitor((type, json) => {
|
wcdbService.setMonitor((type, json) => {
|
||||||
this.handleSessionStatsMonitorChange(type, json)
|
this.handleSessionStatsMonitorChange(type, json)
|
||||||
|
const windows = BrowserWindow.getAllWindows()
|
||||||
// 广播给所有渲染进程窗口
|
// 广播给所有渲染进程窗口
|
||||||
BrowserWindow.getAllWindows().forEach((win) => {
|
windows.forEach((win) => {
|
||||||
if (!win.isDestroyed()) {
|
if (!win.isDestroyed()) {
|
||||||
win.webContents.send('wcdb-change', { type, json })
|
win.webContents.send('wcdb-change', { type, json })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预热 media 数据库列表缓存(后台异步执行)
|
* 预热 media 数据库列表缓存(后台异步执行)
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ export class WcdbCore {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connectMonitorPipe(pipePath)
|
this.connectMonitorPipe(pipePath)
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -194,8 +193,14 @@ export class WcdbCore {
|
|||||||
|
|
||||||
let buffer = ''
|
let buffer = ''
|
||||||
this.monitorPipeClient.on('data', (data: Buffer) => {
|
this.monitorPipeClient.on('data', (data: Buffer) => {
|
||||||
buffer += data.toString('utf8')
|
const rawChunk = data.toString('utf8')
|
||||||
const lines = buffer.split('\n')
|
// macOS 侧可能使用 '\0' 或无换行分隔,统一归一化并兜底拆包
|
||||||
|
const normalizedChunk = rawChunk
|
||||||
|
.replace(/\u0000/g, '\n')
|
||||||
|
.replace(/}\s*{/g, '}\n{')
|
||||||
|
|
||||||
|
buffer += normalizedChunk
|
||||||
|
const lines = buffer.split(/\r?\n/)
|
||||||
buffer = lines.pop() || ''
|
buffer = lines.pop() || ''
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.trim()) {
|
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.on('close', () => {
|
||||||
this.monitorPipeClient = null
|
this.monitorPipeClient = null
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export class WcdbService {
|
|||||||
*/
|
*/
|
||||||
setMonitor(callback: (type: string, json: string) => void): void {
|
setMonitor(callback: (type: string, json: string) => void): void {
|
||||||
this.monitorListener = callback;
|
this.monitorListener = callback;
|
||||||
this.callWorker('setMonitor').catch(() => { });
|
this.callWorker<{ success?: boolean }>('setMonitor').catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,15 +20,17 @@ if (parentPort) {
|
|||||||
result = { success: true }
|
result = { success: true }
|
||||||
break
|
break
|
||||||
case 'setMonitor':
|
case 'setMonitor':
|
||||||
core.setMonitor((type, json) => {
|
{
|
||||||
|
const monitorOk = core.setMonitor((type, json) => {
|
||||||
parentPort!.postMessage({
|
parentPort!.postMessage({
|
||||||
id: -1,
|
id: -1,
|
||||||
type: 'monitor',
|
type: 'monitor',
|
||||||
payload: { type, json }
|
payload: { type, json }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
result = { success: true }
|
result = { success: monitorOk }
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case 'testConnection':
|
case 'testConnection':
|
||||||
result = await core.testConnection(payload.dbPath, payload.hexKey, payload.wxid)
|
result = await core.testConnection(payload.dbPath, payload.hexKey, payload.wxid)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
.update-dialog {
|
.update-dialog {
|
||||||
width: 680px;
|
width: 680px;
|
||||||
background: #f5f5f5;
|
background: var(--bg-secondary, #f5f5f5);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
/* Top Section (White/Gradient) */
|
/* Top Section (White/Gradient) */
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
background: #ffffff;
|
background: var(--bg-primary, #ffffff);
|
||||||
padding: 40px 20px 30px;
|
padding: 40px 20px 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -41,14 +41,14 @@
|
|||||||
left: -50px;
|
left: -50px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
background: radial-gradient(circle, rgba(255, 235, 220, 0.4) 0%, rgba(255, 255, 255, 0) 70%);
|
background: radial-gradient(circle, rgba(255, 235, 220, 0.15) 0%, rgba(255, 255, 255, 0) 70%);
|
||||||
opacity: 0.8;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.version-tag {
|
.version-tag {
|
||||||
background: #f0eee9;
|
background: var(--bg-tertiary, #f0eee9);
|
||||||
color: #8c7b6e;
|
color: var(--text-tertiary, #8c7b6e);
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -60,21 +60,21 @@
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #333333;
|
color: var(--text-primary, #333333);
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
letter-spacing: -0.5px;
|
letter-spacing: -0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #999999;
|
color: var(--text-secondary, #999999);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content Section (Light Gray) */
|
/* Content Section (Light Gray) */
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
background: #f2f2f2;
|
background: var(--bg-tertiary, #f2f2f2);
|
||||||
padding: 24px 40px 40px;
|
padding: 24px 40px 40px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
|
||||||
.icon-box {
|
.icon-box {
|
||||||
background: #fbfbfb; // Beige-ish white
|
background: var(--bg-primary, #fbfbfb);
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: #8c7b6e;
|
color: var(--text-tertiary, #8c7b6e);
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@@ -107,27 +107,38 @@
|
|||||||
.text-box {
|
.text-box {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
h3 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
font-size: 18px;
|
color: var(--text-primary, #333333);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #333333;
|
margin: 16px 0 8px;
|
||||||
margin: 0 0 8px;
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666666;
|
color: var(--text-secondary, #666666);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin: 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin: 8px 0 0 18px;
|
margin: 4px 0 0 18px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666666;
|
color: var(--text-secondary, #666666);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,19 +153,19 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #888;
|
color: var(--text-secondary, #888);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar-bg {
|
.progress-bar-bg {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
background: #e0e0e0;
|
background: var(--border-color, #e0e0e0);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.progress-bar-fill {
|
.progress-bar-fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #000000;
|
background: var(--text-primary, #000000);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
}
|
}
|
||||||
@@ -164,7 +175,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: var(--text-secondary, #666);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +186,8 @@
|
|||||||
|
|
||||||
.btn-ignore {
|
.btn-ignore {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #666666;
|
color: var(--text-secondary, #666666);
|
||||||
border: 1px solid #d0d0d0;
|
border: 1px solid var(--border-color, #d0d0d0);
|
||||||
padding: 16px 32px;
|
padding: 16px 32px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -185,9 +196,9 @@
|
|||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f5f5f5;
|
background: var(--bg-hover, #f5f5f5);
|
||||||
border-color: #999999;
|
border-color: var(--text-secondary, #999999);
|
||||||
color: #333333;
|
color: var(--text-primary, #333333);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -196,11 +207,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-update {
|
.btn-update {
|
||||||
background: #000000;
|
background: var(--text-primary, #000000);
|
||||||
color: #ffffff;
|
color: var(--bg-primary, #ffffff);
|
||||||
border: none;
|
border: none;
|
||||||
padding: 16px 48px;
|
padding: 16px 48px;
|
||||||
border-radius: 20px; // Pill shape
|
border-radius: 20px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -231,7 +242,7 @@
|
|||||||
right: 16px;
|
right: 16px;
|
||||||
background: rgba(0, 0, 0, 0.05);
|
background: rgba(0, 0, 0, 0.05);
|
||||||
border: none;
|
border: none;
|
||||||
color: #999;
|
color: var(--text-secondary, #999);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -244,7 +255,7 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
color: #333;
|
color: var(--text-primary, #333);
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ const UpdateDialog: React.FC<UpdateDialogProps> = ({
|
|||||||
<Quote size={20} />
|
<Quote size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-box">
|
<div className="text-box">
|
||||||
<h3>优化</h3>
|
|
||||||
{updateInfo.releaseNotes ? (
|
{updateInfo.releaseNotes ? (
|
||||||
<div dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }} />
|
<div dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -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 () => {
|
const handleClearAnalyticsCache = async () => {
|
||||||
if (isClearingCache) return
|
if (isClearingCache) return
|
||||||
setIsClearingAnalyticsCache(true)
|
setIsClearingAnalyticsCache(true)
|
||||||
@@ -1427,6 +1442,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
<button className="btn btn-secondary" onClick={handleCopyLog}>
|
<button className="btn btn-secondary" onClick={handleCopyLog}>
|
||||||
<Copy size={16} /> 复制日志内容
|
<Copy size={16} /> 复制日志内容
|
||||||
</button>
|
</button>
|
||||||
|
<button className="btn btn-secondary" onClick={handleClearLog}>
|
||||||
|
<Trash2 size={16} /> 清空日志
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2043,7 +2061,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
|
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
|
||||||
{isCheckingUpdate ? '检查中...' : '检查更新'}
|
{isCheckingUpdate ? '检查中...' : '检查更新'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
@@ -69,6 +69,7 @@ export interface ElectronAPI {
|
|||||||
log: {
|
log: {
|
||||||
getPath: () => Promise<string>
|
getPath: () => Promise<string>
|
||||||
read: () => Promise<{ success: boolean; content?: string; error?: string }>
|
read: () => Promise<{ success: boolean; content?: string; error?: string }>
|
||||||
|
clear: () => Promise<{ success: boolean; error?: string }>
|
||||||
debug: (data: any) => void
|
debug: (data: any) => void
|
||||||
}
|
}
|
||||||
diagnostics: {
|
diagnostics: {
|
||||||
|
|||||||
Reference in New Issue
Block a user