feat: 修复模型路径检查并添加日志显示面板

- 修复 Windows 路径检查问题,支持反斜杠和规范化路径
- 添加日志显示面板到设置弹窗
- 支持查看应用日志和设备信息
- 增强 check-file-exists IPC 处理,记录详细路径信息
- 实现日志标签切换和清空功能
- 拦截 console 输出显示到日志面板
This commit is contained in:
impressionyang 2026-05-20 17:53:17 +08:00
parent 2f7f5aae9e
commit 05d1773415
2 changed files with 239 additions and 4 deletions

View File

@ -131,8 +131,39 @@ ipcMain.handle('copy-to-clipboard', async (_, text: string) => {
ipcMain.handle('check-file-exists', async (_, filePath: string) => { ipcMain.handle('check-file-exists', async (_, filePath: string) => {
try { try {
const exists = existsSync(filePath); // 处理 Windows 路径
logger.info(`IPC: 检查文件存在`, { path: filePath, exists }); 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; return exists;
} catch (error) { } catch (error) {
logger.error(`IPC: 检查文件失败`, { path: filePath, error }); logger.error(`IPC: 检查文件失败`, { path: filePath, error });

View File

@ -434,6 +434,71 @@
.toast.error { .toast.error {
border-color: var(--accent); 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;
}
</style> </style>
</head> </head>
<body> <body>
@ -550,6 +615,21 @@
</div> </div>
</div> </div>
<div class="modal-section">
<h3>📋 运行日志</h3>
<div class="log-tabs">
<button class="log-tab active" data-tab="app-log">应用日志</button>
<button class="log-tab" data-tab="device-log">设备信息</button>
</div>
<button class="btn btn-secondary clear-log-btn" id="clearLogBtn">清空日志</button>
<div id="appLog" class="tab-content log-panel active">
<div class="log-entry info">等待日志输出...</div>
</div>
<div id="deviceLog" class="tab-content log-panel">
<div class="log-entry info">等待设备信息...</div>
</div>
</div>
<div style="display: flex; gap: 8px; margin-top: 20px;"> <div style="display: flex; gap: 8px; margin-top: 20px;">
<button class="btn btn-primary" id="saveSettingsBtn" style="flex: 1;">保存设置</button> <button class="btn btn-primary" id="saveSettingsBtn" style="flex: 1;">保存设置</button>
</div> </div>
@ -577,10 +657,16 @@
const defaultModelSelect = document.getElementById('defaultModelSelect'); const defaultModelSelect = document.getElementById('defaultModelSelect');
const saveSettingsBtn = document.getElementById('saveSettingsBtn'); const saveSettingsBtn = document.getElementById('saveSettingsBtn');
const toast = document.getElementById('toast'); 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 isRecording = false;
let models = []; let models = [];
let settings = {}; let settings = {};
let logEntries = [];
let deviceInfo = {};
// 显示提示 // 显示提示
function showToast(message, type = 'info') { function showToast(message, type = 'info') {
@ -740,8 +826,15 @@
return; return;
} }
// 检查文件是否存在 // 检查文件是否存在 - 处理 Windows 路径
const exists = await window.electronAPI?.checkFileExists(path); 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({ models.push({
name, name,
@ -782,6 +875,117 @@
outputModeSelect.value = settings.outputMode || 'clipboard'; 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 = '<div class="log-entry info">暂无日志</div>';
return;
}
appLog.innerHTML = logEntries.map(entry =>
`<div class="log-entry ${entry.level}">[${entry.timestamp}] ${escapeHtml(entry.message)}</div>`
).join('');
// 滚动到底部
appLog.scrollTop = appLog.scrollHeight;
}
function renderDeviceLog() {
if (Object.keys(deviceInfo).length === 0) {
deviceLog.innerHTML = '<div class="log-entry info">暂无设备信息</div>';
return;
}
deviceLog.innerHTML = Object.entries(deviceInfo).map(([key, value]) =>
`<div class="log-entry info"><strong>${escapeHtml(key)}:</strong> ${escapeHtml(String(value))}</div>`
).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(' '));
};
</script> </script>
</body> </body>
</html> </html>