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(' '));
+ };