From 05d17734152a783a36cb003b213c3e7f9ca560ed Mon Sep 17 00:00:00 2001 From: impressionyang Date: Wed, 20 May 2026 17:53:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=A3=80=E6=9F=A5=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=98=BE=E7=A4=BA=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 Windows 路径检查问题,支持反斜杠和规范化路径 - 添加日志显示面板到设置弹窗 - 支持查看应用日志和设备信息 - 增强 check-file-exists IPC 处理,记录详细路径信息 - 实现日志标签切换和清空功能 - 拦截 console 输出显示到日志面板 --- src/electron-main.ts | 35 +++++++- src/ui/index.html | 208 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 239 insertions(+), 4 deletions(-) diff --git a/src/electron-main.ts b/src/electron-main.ts index 140d18e..5d47991 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -131,8 +131,39 @@ ipcMain.handle('copy-to-clipboard', async (_, text: string) => { ipcMain.handle('check-file-exists', async (_, filePath: string) => { try { - const exists = existsSync(filePath); - logger.info(`IPC: 检查文件存在`, { path: filePath, exists }); + // 处理 Windows 路径 + let checkPath = filePath; + // 规范化路径:将反斜杠转换为正斜杠 + if (checkPath.includes('\\')) { + checkPath = checkPath.replace(/\\/g, '/'); + } + + logger.info('IPC: 检查文件存在', { + originalPath: filePath, + normalizedPath: checkPath, + platform: process.platform + }); + + // 尝试原始路径 + let exists = existsSync(filePath); + logger.info(`检查原始路径:${exists ? '存在' : '不存在'}`, { path: filePath }); + + // 如果原始路径不存在且与规范化路径不同,尝试规范化路径 + if (!exists && checkPath !== filePath) { + exists = existsSync(checkPath); + logger.info(`检查规范化路径:${exists ? '存在' : '不存在'}`, { path: checkPath }); + } + + // 尝试添加前缀 file:/// (针对某些 Windows 情况) + if (!exists && filePath.startsWith('/')) { + const withoutSlash = filePath.slice(1); + exists = existsSync(withoutSlash); + if (exists) { + logger.info(`检查去除前导斜杠路径:存在`, { path: withoutSlash }); + } + } + + logger.info(`IPC: 文件检查结果`, { path: filePath, exists }); return exists; } catch (error) { logger.error(`IPC: 检查文件失败`, { path: filePath, error }); diff --git a/src/ui/index.html b/src/ui/index.html index 3303321..e5ff91c 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -434,6 +434,71 @@ .toast.error { border-color: var(--accent); } + + /* 日志面板 */ + .log-panel { + background: var(--bg-primary); + border-radius: 8px; + padding: 12px; + max-height: 300px; + overflow-y: auto; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 11px; + line-height: 1.4; + } + + .log-entry { + margin-bottom: 4px; + word-break: break-word; + } + + .log-entry.debug { color: #888; } + .log-entry.info { color: #00d9a5; } + .log-entry.warn { color: #ffd700; } + .log-entry.error { color: #ff6b6b; } + + .log-tabs { + display: flex; + gap: 8px; + margin-bottom: 12px; + border-bottom: 1px solid var(--border); + padding-bottom: 8px; + } + + .log-tab { + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 12px; + color: var(--text-secondary); + font-size: 12px; + cursor: pointer; + transition: all 0.2s ease; + } + + .log-tab:hover { + background: var(--border); + color: var(--text-primary); + } + + .log-tab.active { + background: var(--accent); + border-color: var(--accent); + color: white; + } + + .tab-content { + display: none; + } + + .tab-content.active { + display: block; + } + + .clear-log-btn { + float: right; + margin-top: -24px; + } @@ -550,6 +615,21 @@ + +
@@ -577,10 +657,16 @@ const defaultModelSelect = document.getElementById('defaultModelSelect'); const saveSettingsBtn = document.getElementById('saveSettingsBtn'); const toast = document.getElementById('toast'); + const clearLogBtn = document.getElementById('clearLogBtn'); + const appLog = document.getElementById('appLog'); + const deviceLog = document.getElementById('deviceLog'); + const logTabs = document.querySelectorAll('.log-tab'); let isRecording = false; let models = []; let settings = {}; + let logEntries = []; + let deviceInfo = {}; // 显示提示 function showToast(message, type = 'info') { @@ -740,8 +826,15 @@ return; } - // 检查文件是否存在 - const exists = await window.electronAPI?.checkFileExists(path); + // 检查文件是否存在 - 处理 Windows 路径 + let checkPath = path; + // 将反斜杠转换为正斜杠进行检查 + if (checkPath.includes('\\\\')) { + checkPath = checkPath.replace(/\\\\/g, '/'); + } + // 尝试原始路径和转换后的路径 + const exists = (await window.electronAPI?.checkFileExists(path)) || + (checkPath !== path ? await window.electronAPI?.checkFileExists(checkPath) : false); models.push({ name, @@ -782,6 +875,117 @@ outputModeSelect.value = settings.outputMode || 'clipboard'; } }); + + // 日志功能 + function addLogEntry(level, message) { + const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19); + const entry = { level, message, timestamp }; + logEntries.push(entry); + + // 限制日志条目数量 + if (logEntries.length > 500) { + logEntries.shift(); + } + + renderAppLog(); + } + + function renderAppLog() { + if (logEntries.length === 0) { + appLog.innerHTML = '
暂无日志
'; + return; + } + + appLog.innerHTML = logEntries.map(entry => + `
[${entry.timestamp}] ${escapeHtml(entry.message)}
` + ).join(''); + + // 滚动到底部 + appLog.scrollTop = appLog.scrollHeight; + } + + function renderDeviceLog() { + if (Object.keys(deviceInfo).length === 0) { + deviceLog.innerHTML = '
暂无设备信息
'; + return; + } + + deviceLog.innerHTML = Object.entries(deviceInfo).map(([key, value]) => + `
${escapeHtml(key)}: ${escapeHtml(String(value))}
` + ).join(''); + } + + function clearLogs() { + logEntries = []; + deviceInfo = {}; + renderAppLog(); + renderDeviceLog(); + showToast('日志已清空', 'success'); + } + + // 日志标签切换 + logTabs.forEach(tab => { + tab.addEventListener('click', () => { + logTabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + + const tabName = tab.dataset.tab; + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.getElementById(tabName === 'app-log' ? 'appLog' : 'deviceLog').classList.add('active'); + }); + }); + + // 清空日志按钮 + clearLogBtn.addEventListener('click', clearLogs); + + // 监听录音事件并记录日志 + recordBtn.addEventListener('click', async () => { + if (isRecording) { + addLogEntry('info', '🔴 停止录音'); + } else { + addLogEntry('info', '🎤 开始录音,请求麦克风权限...'); + // 获取设备信息 + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioInputs = devices.filter(d => d.kind === 'audioinput'); + deviceInfo.audioInputs = audioInputs.map(d => d.label || `设备 ${d.deviceId.slice(0, 8)}...`); + deviceInfo.count = audioInputs.length; + renderDeviceLog(); + addLogEntry('info', `找到 ${audioInputs.length} 个音频输入设备`); + } catch (err) { + addLogEntry('error', '获取设备列表失败:' + err.message); + } + } + }); + + // 拦截 console.log 用于显示日志 + const originalConsole = { + log: console.log, + warn: console.warn, + error: console.error, + info: console.info + }; + + console.log = function(...args) { + originalConsole.log.apply(console, args); + const msg = args.join(' '); + if (msg.includes('[AudioRecorder]') || msg.includes('[SpeechRecognizer]')) { + const level = msg.includes('ERROR') ? 'error' : msg.includes('WARN') ? 'warn' : msg.includes('DEBUG') ? 'debug' : 'info'; + addLogEntry(level, msg); + } + }; + + console.error = function(...args) { + originalConsole.error.apply(console, args); + addLogEntry('error', args.join(' ')); + }; + + console.warn = function(...args) { + originalConsole.warn.apply(console, args); + addLogEntry('warn', args.join(' ')); + };