feat: 修复模型路径检查并添加日志显示面板
- 修复 Windows 路径检查问题,支持反斜杠和规范化路径 - 添加日志显示面板到设置弹窗 - 支持查看应用日志和设备信息 - 增强 check-file-exists IPC 处理,记录详细路径信息 - 实现日志标签切换和清空功能 - 拦截 console 输出显示到日志面板
This commit is contained in:
parent
2f7f5aae9e
commit
05d1773415
@ -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 });
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user