yan5xu/ququ/main 80k tokens More Tools
```
├── .env.example (omitted)
├── .gitignore (300 tokens)
├── .python-version
├── AGENTS.md (700 tokens)
├── LICENSE (omitted)
├── README.md (1300 tokens)
├── assets/
   ├── icon.icns
   ├── icon.ico
   ├── icon.png
   ├── wechat-community-qrcode.png
├── cleanup.js (300 tokens)
├── components.json (100 tokens)
├── download_models.py (700 tokens)
├── entitlements.mac.plist (300 tokens)
├── funasr_server.py (3.6k tokens)
├── jsconfig.json
├── main.js (1400 tokens)
├── package.json (1000 tokens)
├── pnpm-lock.yaml (omitted)
├── pnpm-workspace.yaml
├── postcss.config.js
├── preload.js (1500 tokens)
├── pyproject.toml (100 tokens)
├── scripts/
   ├── prepare-embedded-python.js (2.8k tokens)
   ├── test-embedded-python.js (1400 tokens)
├── src/
   ├── App.jsx (5k tokens)
   ├── components/
      ├── SettingsPanel.jsx (600 tokens)
      ├── ui/
         ├── button.jsx (300 tokens)
         ├── card.jsx (300 tokens)
         ├── history-modal.jsx (1700 tokens)
         ├── input.jsx (100 tokens)
         ├── label.jsx (100 tokens)
         ├── loading-dots.jsx (100 tokens)
         ├── model-status-indicator.jsx (1600 tokens)
         ├── permission-card.jsx (300 tokens)
         ├── sonner.jsx (200 tokens)
         ├── status-light.jsx (400 tokens)
         ├── tabs.jsx (300 tokens)
   ├── helpers/
      ├── clipboard.js (2.1k tokens)
      ├── database.js (1000 tokens)
      ├── environment.js (1000 tokens)
      ├── funasrManager.js (7.5k tokens)
      ├── hotkeyManager.js (900 tokens)
      ├── ipcHandlers.js (7.3k tokens)
      ├── logManager.js (900 tokens)
      ├── pythonInstaller.js (2.2k tokens)
      ├── tray.js (700 tokens)
      ├── windowManager.js (1100 tokens)
   ├── history.html (100 tokens)
   ├── history.jsx (2.3k tokens)
   ├── hooks/
      ├── useHotkey.js (600 tokens)
      ├── useModelStatus.js (1600 tokens)
      ├── usePermissions.js (400 tokens)
      ├── useRecording.js (2.4k tokens)
      ├── useTextProcessing.js (1800 tokens)
      ├── useWindowDrag.js (300 tokens)
   ├── index.css (2k tokens)
   ├── index.html (300 tokens)
   ├── lib/
      ├── utils.ts
   ├── main.jsx (1200 tokens)
   ├── settings.html (100 tokens)
   ├── settings.jsx (4.4k tokens)
   ├── utils/
      ├── process.js (400 tokens)
   ├── vite.config.js (500 tokens)
├── tailwind.config.js (400 tokens)
├── test_funasr_timing.py (300 tokens)
├── test_text_insertion.js (1400 tokens)
├── test_text_processing_modes.js (500 tokens)
├── uv.lock (omitted)
```


## /.gitignore

```gitignore path="/.gitignore" 
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
yarn.lock*


# Production builds
dist/
build/
out/

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Logs
*.log
logs/
funasr_bridge.log
funasr_server.log

# Runtime data
pids/
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# nyc test coverage
.nyc_output

# Dependency directories
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Electron specific
app/
release/
*.dmg
*.exe
*.deb
*.rpm
*.AppImage

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/

# FunASR models and cache
models/
cache/
*.model
*.bin

# Audio files
*.wav
*.mp3
*.m4a
*.aac
*.flac

# Database files
*.db
*.sqlite
*.sqlite3

# Temporary files
tmp/
temp/
*.tmp
*.temp

# Build tools
.gyp
.gyprc

python

```

## /.python-version

```python-version path="/.python-version" 
3.11

```

## /AGENTS.md

# AGENTS.md

此文件为在此代码库中工作的AI助手提供指导。

## 项目管理

- **GitHub Project**: https://github.com/users/yan5xu/projects/2
- 所有任务、功能开发和Bug跟踪都在项目看板中管理
- 开发进度和里程碑规划可在项目看板中查看

## 非标准构建命令

- `pnpm run dev` - 同时运行渲染进程(Vite)和主进程(Electron)
- `pnpm run dev:renderer` - Vite开发服务器必须从`src/`目录运行(不是根目录)
- `pnpm run build:renderer` - 任何Electron构建命令之前都必须先执行此命令
- `pnpm run prepare:python` - 下载并准备嵌入式Python环境(包含所有依赖)
- `pnpm run prepare:python:info` - 查看嵌入式Python环境信息
- `pnpm run test:python` - 测试嵌入式Python环境是否正常工作
- `pnpm run test:python:info` - 显示Python环境测试详细信息
- `pnpm run clean` - 清理构建文件和Python环境
- 所有构建命令(`build:mac`, `build:win`, `build:linux`)现在自动执行`prepare:python`

## 关键架构模式

### FunASR服务器通信
- Python服务器(`funasr_server.py`)通过stdin/stdout进行JSON消息通信
- 音频转录前必须启动服务器(由`funasrManager.js`处理)
- 音频文件在系统临时目录创建,不在项目目录
- FunASR模型下载到用户数据目录,不是项目目录
- 支持模型自动下载和状态监控(`download_models.py`)
- 模型缺失时提供优雅的错误处理和下载提示
- 新增模型文件检查机制,避免未下载模型时的初始化错误

### IPC架构(非标准)
- 所有Electron IPC处理器集中在`src/helpers/ipcHandlers.js`
- F2热键使用自定义双击检测,带发送者跟踪以防止内存泄漏
- 录音状态通过`hotkeyManager.js`在主进程和渲染进程间同步
- 新增模型管理IPC接口:`check-model-files`, `download-models`, `get-download-progress`
- 模型下载进度通过`model-download-progress`事件实时推送

### 窗口管理
- 主窗口和控制面板是独立的BrowserWindow实例
- 历史窗口加载`src/history.html`(与主应用分离的入口点)
- 所有窗口使用`preload.js`进行安全API暴露

### 数据库架构
- 使用better-sqlite3,自定义架构在`src/helpers/database.js`
- 转录表同时存储raw_text(FunASR)和processed_text(AI优化)
- 设置在键值表中JSON序列化存储

## 项目特定约定

### 文件组织
- `src/helpers/`中的文件是管理器类(不是工具函数)
- `src/hooks/`中的钩子遵循Electron集成的自定义模式
- Python脚本(`funasr_server.py`, `download_models.py`)在项目根目录,不在src/
- `scripts/`目录包含构建时脚本:
  - `prepare-embedded-python.js` - 嵌入式Python环境准备
  - `test-embedded-python.js` - Python环境测试和验证
- `python/`目录包含嵌入式Python运行时(构建时生成,在.gitignore中)

### 环境变量
- `ELECTRON_USER_DATA`由主进程设置,供Python脚本日志使用
- AI API配置通过应用内设置面板进行配置
- 开发模式通过`NODE_ENV=development`检测

### CSS架构
- 使用Tailwind 4.x,带中文字体优化
- 自定义CSS类:`.chinese-content`、`.chinese-title`、`.status-text`
- 硬编码WCAG 2.1兼容的对比度比例在CSS变量中
- Electron特定类:`.draggable`、`.non-draggable`

### 音频处理
- 音频以WAV格式在临时文件中处理
- FunASR处理VAD(语音活动检测)和标点恢复
- AI文本处理在FunASR转录完成后进行

### 日志管理
- 必须使用`src/helpers/logManager.js`而非console.log
- 应用日志和FunASR日志分别存储在用户数据目录
- 提供`logFunASR()`方法专门记录FunASR相关日志
- 日志以JSON格式存储,支持结构化查询
- 嵌入式Python环境通过`ELECTRON_USER_DATA`环境变量获取日志路径

## 关键注意事项

### 路径解析
- Vite配置使用`src/`作为基础目录,影响所有相对导入
- 生产构建引用`app.asar.unpacked`中的Python脚本和嵌入式Python环境
- 资源路径从src目录使用`../assets`
- 嵌入式Python环境路径:
  - 开发模式:`项目根目录/python/bin/python3.11`
  - 生产模式:`process.resourcesPath/app.asar.unpacked/python/bin/python3.11`

### Python集成
- 使用完全隔离的嵌入式Python环境(Python 3.11.6)
- 嵌入式环境包含所有必需依赖:numpy<2, torch==2.0.1, torchaudio==2.0.2, librosa>=0.11.0, funasr>=1.2.7
- FunASR安装需要特定模型版本(v2.0.4)
- Python进程生成使用`windowsHide: true`选项
- 完全隔离的环境变量设置:PYTHONHOME, PYTHONPATH, PYTHONDONTWRITEBYTECODE
- 清除系统Python环境变量干扰:PYTHONUSERBASE, PYTHONSTARTUP, VIRTUAL_ENV
- macOS代码签名权限配置支持Python扩展和JIT编译(`entitlements.mac.plist`)

### 状态管理
- 无外部状态库 - 使用React hooks配合Electron IPC
- 录音状态必须在进程间手动同步
- 窗口可见性状态影响热键注册

### 开发vs生产环境
- 开发模式有2秒延迟等待Vite启动
- 生产模式使用嵌入式Python环境,无需系统Python依赖
- 日志文件位置在开发和生产构建中不同
- 构建流程自动准备嵌入式Python环境和模型文件
- 构建产物包含完整的Python运行时(约1GB+)

## 新增功能架构

### 嵌入式Python环境
- 基于python-build-standalone项目的独立Python运行时
- 支持macOS ARM64和x86_64架构自动检测
- 包含完整的科学计算栈:numpy, torch, librosa等
- 构建时自动下载、安装和验证所有依赖
- 生产环境完全独立,不依赖系统Python

### 模型管理系统
- 三个核心模型:ASR(语音识别)、VAD(语音活动检测)、PUNC(标点恢复)
- 模型文件检查机制,支持大小和完整性验证
- 并行下载所有模型,支持实时进度显示
- 模型缺失时的优雅降级和用户提示
- 模型状态指示器组件提供可视化反馈

### 构建系统增强
- macOS代码签名和公证支持
- 嵌入式Python环境自动打包
- 构建前自动准备Python环境和依赖验证
- 支持清理命令移除Python环境和构建缓存

## /README.md

<div align="center">

<!-- 在这里放置您的Logo图片 -->
<!-- 例如: <img src="assets/logo.png" width="150" /> -->
<br/>
<br/>

# 蛐蛐 (QuQu)

**开源免费的 Wispr Flow 替代方案 | 为中文而生的下一代智能语音工作流**

</div>

<div align="center">

<!-- 徽章 (Badges) - 您可以后续替换为动态徽章服务 (如 shields.io) -->
<img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey" alt="Platform">
<img src="https://img.shields.io/badge/release-v1.0.0-brightgreen" alt="Release">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome">

</div>

<br/>

> **厌倦了 Wispr Flow 的订阅费用?寻找开源免费的语音输入方案?来试试「蛐蛐」!**

**蛐蛐 (QuQu)** 是 **Wispr Flow 的开源免费替代方案**,专为中文用户打造的注重隐私的桌面端语音输入与文本处理工具。与 Wispr Flow 不同,蛐蛐完全开源免费,数据本地处理,专为中文优化,支持国产AI模型。

### 🆚 vs Wispr Flow:开源免费的替代方案

| 核心对比 | 🎯 蛐蛐 (QuQu) | 💰 Wispr Flow |
|---------|---------------|---------------|
| **价格** | ✅ **完全免费** | ❌ $12/月订阅 |
| **隐私** | ✅ **本地处理** | ❌ 云端处理 |
| **中文** | ✅ **专为中文优化** | ⚠️ 通用支持 |
| **AI模型** | ✅ **国产AI支持** | ❌ 仅国外模型 |

想象一下,你可以像和朋友聊天一样写作。说的内容被实时、精准地转换成文字,口误和"嗯、啊"等废话被自动修正,甚至能根据你的要求,自动整理成邮件格式或代码片段。**这就是「蛐蛐」为你带来的体验 —— 而且完全免费!**

---

## ✨ 核心优势

| 特性 | 蛐蛐 (QuQu) 的解决方案 |
| :--- | :--- |
| 🎯 **顶尖中文识别,隐私至上** | 内置阿里巴巴 **FunASR Paraformer** 模型,在您的电脑本地运行。这意味着它能听懂中文互联网的"梗",也能保护您最私密的语音数据不被上传。 |
| 💡 **会思考的"两段式引擎"** | 独创 **"ASR精准识别 + LLM智能优化"** 工作流。它不仅能转录,更能"理解"和"重塑"您的语言。**自动过滤口头禅**、**修正错误表述**(例如将"周三开会,不对,是周四"直接输出为"周四开会"),这些都只是基础操作。 |
| 🌐 **为国内优化的开放AI生态** | 支持任何兼容OpenAI API的服务,并**优先适配国内顶尖模型** (如通义千问、Kimi等)。这意味着更快的响应速度、更低的费用和更好的合规性。 |
| 🚀 **开发者与效率专家挚爱** | 能准确识别并格式化 `camelCase` 和 `snake_case` 等编程术语。通过自定义AI指令,更能实现**上下文感知**,根据您当前的应用(写代码、回邮件)智能调整输出格式。 |


## 🎬 功能演示

<!-- 在这里放置您的GIF演示图 -->
<!-- 例如: <img src="assets/demo.gif" /> -->
<p align="center"><i>(这里是应用的GIF演示图)</i></p>

- **一键唤醒**: 全局快捷键 F2,随时随地开始记录。
- **实时识别**: 本地 FunASR 引擎提供高精度中文识别。
- **智能优化**: 连接您的AI模型,自动润色、纠错、总结。
- **无缝粘贴**: 转换完成的文本自动粘贴到您当前光标位置。

### 🚀 从 Wispr Flow 迁移?

如果你正在使用 Wispr Flow 但希望**节省订阅费用**、**保护隐私数据**、**更好的中文支持**,那么蛐蛐就是你的完美选择!

## 🚀 快速开始

### 1. 环境要求
- **Node.js 18+** 和 pnpm
- **Python 3.8+** (用于运行本地FunASR服务)
- **macOS 10.15+**, **Windows 10+**, 或 **Linux**

### 2. 项目初始化

#### 方案一:使用 uv (推荐) 🌟

[uv](https://github.com/astral-sh/uv) 是现代化的 Python 包管理器,能自动管理 Python 版本和依赖,避免环境冲突:

```bash
# 1. 克隆项目
git clone https://github.com/yan5xu/ququ.git
cd ququ

# 2. 安装 Node.js 依赖
pnpm install

# 3. 安装 uv (如果尚未安装)
# macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows:
# powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# 4. 初始化 Python 环境 (uv 会自动下载 Python 3.11 和所有依赖)
uv sync

# 5. 下载 FunASR 模型
uv run python download_models.py

# 6. 启动应用!
pnpm run dev
```

#### 方案二:使用系统 Python

如果您更喜欢使用系统 Python 环境:

```bash
# 1. 克隆项目
git clone https://github.com/yan5xu/ququ.git
cd ququ

# 2. 安装 Node.js 依赖
pnpm install

# 3. 创建虚拟环境 (推荐)
python3 -m venv .venv
source .venv/bin/activate  # Linux/macOS
# .venv\Scripts\activate   # Windows

# 4. 安装 Python 依赖
pip install funasr modelscope torch torchaudio librosa numpy

# 5. 下载 FunASR 模型
python download_models.py

# 6. 启动应用!
pnpm run dev
```

#### 方案三:使用嵌入式 Python 环境

项目还支持完全隔离的嵌入式 Python 环境(主要用于生产构建):

```bash
# 1-2. 同上克隆项目和安装 Node.js 依赖

# 3. 准备嵌入式 Python 环境
pnpm run prepare:python

# 4. 测试环境是否正常
pnpm run test:python

# 5. 启动应用
pnpm run dev
```

### 3. 配置AI模型
启动应用后,在 **设置页面** 中填入您的AI服务商提供的 **API Key**、**Base URL** 和 **模型名称**。支持通义千问、Kimi、智谱AI等国产模型,配置将自动保存在本地。

### 4. 故障排除

#### 常见初始化问题

**问题**: `ModuleNotFoundError: No module named 'funasr'`
```bash
# 解决方案 1: 使用 uv (推荐)
uv sync
uv run python download_models.py

# 解决方案 2: 重新安装依赖
pip install funasr modelscope torch torchaudio librosa numpy

# 解决方案 3: 使用嵌入式环境
pnpm run prepare:python
```

**问题**: FunASR 模型下载失败或加载缓慢
```bash
# 检查网络连接,确保能访问 modelscope.cn
# 如果在 macOS 上遇到 SSL 警告:
pip install "urllib3<2.0"

# 手动下载模型:
python download_models.py
# 或使用 uv:
uv run python download_models.py
```

**问题**: Python 版本不兼容
```bash
# 使用 uv 自动管理 Python 版本 (推荐)
uv sync  # 会自动下载 Python 3.11

# 或手动安装 Python 3.8+
# 检查当前版本: python3 --version
```

#### 环境选择建议

| 使用场景 | 推荐方案 | 优点 |
|---------|---------|------|
| **新用户/快速体验** | uv | 自动管理,无环境冲突 |
| **开发者/自定义需求** | 系统 Python + 虚拟环境 | 灵活控制,便于调试 |
| **生产部署** | 嵌入式环境 | 完全隔离,无外部依赖 |

#### 其他常见问题

- **权限问题**: 在某些系统上可能需要使用 `--user` 参数安装Python包
- **网络问题**: 首次运行时需要下载FunASR模型,请确保网络连接正常
- **模型路径**: 模型默认下载到 `~/.cache/modelscope/` 目录

## 🛠️ 技术栈

- **前端**: React 19, TypeScript, Tailwind CSS, shadcn/ui, Vite
- **桌面端**: Electron
- **语音技术 (本地)**: FunASR (Paraformer-large, FSMN-VAD, CT-Transformer)
- **AI模型 (可配置)**: 兼容 OpenAI, Anthropic, 阿里云通义千问, Kimi 等
- **数据库**: better-sqlite3

## 🤝 参与贡献

我们是一个开放和友好的社区,欢迎任何形式的贡献!

### 📋 项目管理

我们使用 GitHub Projects 来管理项目的开发进度和任务规划:

- 📊 **项目看板**: [蛐蛐 开发看板](https://github.com/users/yan5xu/projects/2) - 查看当前开发状态、功能规划和进度跟踪
- 🎯 **任务管理**: 所有功能开发、Bug修复和改进建议都在项目看板中进行跟踪
- 🔄 **开发流程**: 从想法提出到功能发布的完整流程可视化

### 如何参与

- 🤔 **提建议**: 对产品有任何想法?欢迎到 [Issues](https://github.com/yan5xu/ququ/issues) 页面提出。
- 🐛 **报Bug**: 发现程序出错了?请毫不犹豫地告诉我们。
- 💻 **贡献代码**: 如果您想添加新功能或修复Bug,请参考以下步骤:
    1.  Fork 本项目
    2.  创建您的特性分支 (`git checkout -b feature/your-amazing-feature`)
    3.  提交您的更改 (`git commit -m 'feat: Add some amazing feature'`)
    4.  将您的分支推送到远程 (`git push origin feature/your-amazing-feature`)
    5.  创建一个 Pull Request

## 💬 交流与社区 (Communication & Community)

「蛐蛐」是一个由社区驱动的开源项目,我们相信开放的交流能激发最好的创意。你的每一个想法、每一次反馈都对项目至关重要。

我们诚挚地邀请你加入官方微信交流群,在这里你可以:

*   🚀 **获取一手资讯**:第一时间了解项目更新、新功能预告和开发路线图。
*   💬 **直接与开发者对话**:遇到安装难题?有绝妙的功能点子?在群里可以直接 @ 作者和核心贡献者。
*   💡 **分享与学习**:交流你的 AI 指令 (Prompt) 和自动化工作流,看看别人是怎么把「蛐蛐」玩出花的。
*   🤝 **参与项目共建**:从一个想法的提出,到一次代码的提交 (Pull Request),社区是你最好的起点。

<div align="center">

| 微信扫码,加入官方交流群 |
| :----------------------------------------------------------: |
| <img src="assets/wechat-community-qrcode.png" width="200" alt="QuQu Official WeChat Group" /> <br> *QuQu Official WeChat Group* |
| <p style="font-size:12px; color: #888;">如果二维码过期或无法加入,请在 <a href="https://github.com/yan5xu/ququ/issues">Issues</a> 中提一个 Issue 提醒我们,谢谢!</p> |

</div>

## 🙏 致谢

本项目的诞生离不开以下优秀项目的启发和支持:

- [FunASR](https://github.com/modelscope/FunASR): 阿里巴巴开源的工业级语音识别工具包。
- [OpenWhispr](https://github.com/HeroTools/open-whispr): 为本项目提供了优秀的架构参考。
- [shadcn/ui](https://ui.shadcn.com/): 提供了高质量、可组合的React组件。

## 📄 许可证

本项目采用 [Apache License 2.0](LICENSE) 许可证。

## /assets/icon.icns

Binary file available at https://raw.githubusercontent.com/yan5xu/ququ/refs/heads/main/assets/icon.icns

## /assets/icon.ico

Binary file available at https://raw.githubusercontent.com/yan5xu/ququ/refs/heads/main/assets/icon.ico

## /assets/icon.png

Binary file available at https://raw.githubusercontent.com/yan5xu/ququ/refs/heads/main/assets/icon.png

## /assets/wechat-community-qrcode.png

Binary file available at https://raw.githubusercontent.com/yan5xu/ququ/refs/heads/main/assets/wechat-community-qrcode.png

## /cleanup.js

```js path="/cleanup.js" 
const fs = require('fs');
const path = require('path');

console.log('🧹 清理蛐蛐项目...');

// 需要清理的目录和文件
const cleanupTargets = [
  'src/dist',
  'dist',
  'node_modules/.cache',
  'cache',
  '*.log',
  'funasr_bridge.log'
];

// 递归删除目录
function removeDir(dirPath) {
  if (fs.existsSync(dirPath)) {
    fs.rmSync(dirPath, { recursive: true, force: true });
    console.log(`🗑️  删除目录: ${dirPath}`);
  }
}

// 删除文件
function removeFile(filePath) {
  if (fs.existsSync(filePath)) {
    fs.unlinkSync(filePath);
    console.log(`🗑️  删除文件: ${filePath}`);
  }
}

// 清理构建产物
const distPath = path.join(__dirname, 'src', 'dist');
const buildPath = path.join(__dirname, 'dist');
removeDir(distPath);
removeDir(buildPath);

// 清理缓存
const cachePath = path.join(__dirname, 'cache');
const nodeModulesCachePath = path.join(__dirname, 'node_modules', '.cache');
removeDir(cachePath);
removeDir(nodeModulesCachePath);

// 清理日志文件
const logFiles = [
  path.join(__dirname, 'funasr_bridge.log'),
  path.join(__dirname, 'electron.log'),
  path.join(__dirname, 'main.log')
];

logFiles.forEach(logFile => {
  removeFile(logFile);
});

// 清理临时文件
const tempFiles = fs.readdirSync(__dirname).filter(file => 
  file.endsWith('.tmp') || 
  file.endsWith('.temp') ||
  file.startsWith('temp_')
);

tempFiles.forEach(tempFile => {
  removeFile(path.join(__dirname, tempFile));
});

console.log('✅ 清理完成!');
console.log('💡 提示: 运行 pnpm install 重新安装依赖');
```

## /components.json

```json path="/components.json" 
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": false,
  "tsx": false,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "slate",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "src/components",
    "utils": "src/lib/utils"
  }
}
```

## /download_models.py

```py path="/download_models.py" 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FunASR模型下载脚本
并行下载所有模型文件
"""

import sys
import json
import threading
import time

def download_model(model_config, progress_callback=None):
    """下载单个模型"""
    model_name = model_config["name"]
    model_type = model_config["type"]
    
    try:
        from funasr import AutoModel
        
        if progress_callback:
            progress_callback(model_type, "downloading", 0)
        
        # 下载模型
        AutoModel(
            model=model_name,
            model_revision="v2.0.4"
        )
        
        if progress_callback:
            progress_callback(model_type, "completed", 100)
            
        return {"success": True, "model": model_type}
        
    except Exception as e:
        if progress_callback:
            progress_callback(model_type, "error", 0, str(e))
        return {"success": False, "model": model_type, "error": str(e)}

def main():
    """主函数:并行下载所有模型"""
    
    # 模型配置
    models = [
        {
            "name": "damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
            "type": "asr"
        },
        {
            "name": "damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
            "type": "vad"
        },
        {
            "name": "damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
            "type": "punc"
        }
    ]
    
    # 进度跟踪
    progress = {"asr": 0, "vad": 0, "punc": 0}
    results = {}
    completed_count = 0
    total_count = len(models)
    
    def progress_callback(model_type, stage, percent, error=None):
        nonlocal completed_count
        
        if stage == "downloading":
            progress[model_type] = percent
        elif stage == "completed":
            progress[model_type] = 100
            completed_count += 1
        elif stage == "error":
            progress[model_type] = 0
            completed_count += 1
        
        # 计算总体进度
        overall_progress = sum(progress.values()) / total_count
        
        # 输出进度信息
        status = {
            "stage": stage,
            "model": model_type,
            "progress": percent,
            "overall_progress": round(overall_progress, 1),
            "completed": completed_count,
            "total": total_count
        }
        
        if error:
            status["error"] = error
            
        print(json.dumps(status, ensure_ascii=False))
        sys.stdout.flush()
    
    # 启动并行下载线程
    threads = []
    for model_config in models:
        thread = threading.Thread(
            target=lambda config=model_config: results.update({
                config["type"]: download_model(config, progress_callback)
            })
        )
        thread.start()
        threads.append(thread)
    
    # 等待所有线程完成
    for thread in threads:
        thread.join()
    
    # 检查结果
    failed_models = [model_type for model_type, result in results.items() if not result["success"]]
    
    if failed_models:
        final_result = {
            "success": False,
            "error": f"以下模型下载失败: {', '.join(failed_models)}",
            "failed_models": failed_models,
            "results": results
        }
    else:
        final_result = {
            "success": True,
            "message": "所有模型下载完成",
            "results": results
        }
    
    print(json.dumps(final_result, ensure_ascii=False))
    sys.stdout.flush()

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        error_result = {
            "success": False,
            "error": str(e)
        }
        print(json.dumps(error_result, ensure_ascii=False))
        sys.exit(1)
```

## /entitlements.mac.plist

```plist path="/entitlements.mac.plist" 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- 允许应用访问网络 -->
    <key>com.apple.security.network.client</key>
    <true/>
    
    <!-- 允许应用访问网络服务器 -->
    <key>com.apple.security.network.server</key>
    <true/>
    
    <!-- 允许应用访问文件系统 -->
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    
    <!-- 允许应用访问下载目录 -->
    <key>com.apple.security.files.downloads.read-write</key>
    <true/>
    
    <!-- 允许应用访问用户数据目录 -->
    <key>com.apple.security.files.user-selected.read-only</key>
    <true/>
    
    <!-- 允许应用执行外部程序(Python) -->
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    
    <!-- 允许应用加载动态库 -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    
    <!-- 允许应用禁用库验证(Python扩展需要) -->
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
    
    <!-- 允许应用访问麦克风 -->
    <key>com.apple.security.device.microphone</key>
    <true/>
    
    <!-- 允许应用访问辅助功能(文本插入需要) -->
    <key>com.apple.security.automation.apple-events</key>
    <true/>
    
    <!-- 允许应用注册全局热键 -->
    <key>com.apple.security.temporary-exception.apple-events</key>
    <array>
        <string>com.apple.systemevents</string>
    </array>
    
    <!-- 允许应用在后台运行 -->
    <key>com.apple.security.app-sandbox</key>
    <false/>
    
    <!-- JIT编译支持(PyTorch需要) -->
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
</dict>
</plist>
```

## /funasr_server.py

```py path="/funasr_server.py" 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FunASR模型服务器
保持模型在内存中,通过stdin/stdout进行通信
"""

import sys
import json
import os
import logging
import traceback
import signal
import contextlib
import io
import argparse
import glob
from pathlib import Path

# 设置日志
import tempfile
import os


# 获取日志文件路径
def get_log_path():
    # 尝试从环境变量获取用户数据目录
    if "ELECTRON_USER_DATA" in os.environ:
        log_dir = os.path.join(os.environ["ELECTRON_USER_DATA"], "logs")
    else:
        # 回退到临时目录
        log_dir = os.path.join(tempfile.gettempdir(), "ququ_logs")

    # 确保日志目录存在
    os.makedirs(log_dir, exist_ok=True)
    return os.path.join(log_dir, "funasr_server.log")


log_file_path = get_log_path()

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler(log_file_path, encoding="utf-8"),
        logging.StreamHandler(),  # 同时输出到控制台
    ],
)
logger = logging.getLogger(__name__)

# 记录日志文件位置
logger.info(f"FunASR服务器日志文件: {log_file_path}")


@contextlib.contextmanager
def suppress_stdout():
    """上下文管理器:临时重定向stdout到devnull,避免FunASR库的非JSON输出干扰IPC通信"""
    old_stdout = sys.stdout
    devnull = open(os.devnull, "w")
    try:
        sys.stdout = devnull
        yield
    finally:
        sys.stdout = old_stdout
        devnull.close()


class FunASRServer:
    def __init__(self, damo_root=None):
        self.asr_model = None
        self.vad_model = None
        self.punc_model = None
        self.initialized = False
        self.running = True
        self.transcription_count = 0
        self.total_audio_duration = 0.0

        # 外部传入的 damo 根目录(例如 /Volumes/APFS/AI/models/damo)
        self.damo_root = damo_root or os.environ.get("DAMO_ROOT")

        signal.signal(signal.SIGTERM, self._signal_handler)
        signal.signal(signal.SIGINT, self._signal_handler)
        self._setup_runtime_environment()

    def _setup_runtime_environment(self):
        """设置运行时环境变量以优化性能"""
        try:
            import os

            # 设置线程数优化
            os.environ["OMP_NUM_THREADS"] = "4"
            logger.info("运行时环境变量设置完成")
        except Exception as e:
            logger.warning(f"环境设置失败: {str(e)}")

    def _signal_handler(self, signum, frame):
        """处理退出信号"""
        logger.info(f"收到信号 {signum},准备退出...")
        self.running = False

    def _load_asr_model(self):
        """加载ASR模型"""
        try:
            logger.info("开始加载ASR模型...")
            with suppress_stdout():
                from funasr import AutoModel

                self.asr_model = AutoModel(
                    model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
                    model_revision="v2.0.4",
                    disable_update=True,
                    device="cpu",
                )
            logger.info("ASR模型加载完成")
            return True
        except Exception as e:
            logger.error(f"ASR模型加载失败: {str(e)}")
            return False

    def _load_vad_model(self):
        """加载VAD模型"""
        try:
            logger.info("开始加载VAD模型...")
            with suppress_stdout():
                from funasr import AutoModel

                self.vad_model = AutoModel(
                    model="damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
                    model_revision="v2.0.4",
                    disable_update=True,
                    device="cpu",
                )
            logger.info("VAD模型加载完成")
            return True
        except Exception as e:
            logger.error(f"VAD模型加载失败: {str(e)}")
            return False

    def _load_punc_model(self):
        """加载标点恢复模型"""
        try:
            import time

            start_time = time.time()
            logger.info("开始加载标点恢复模型...")

            # 记录导入时间
            import_start = time.time()
            with suppress_stdout():
                from funasr import AutoModel
            import_time = time.time() - import_start
            logger.info(f"FunASR导入耗时: {import_time:.2f}秒")

            # 记录模型创建时间
            model_start = time.time()
            with suppress_stdout():
                self.punc_model = AutoModel(
                    model="damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
                    model_revision="v2.0.4",
                    disable_update=True,
                    device="cpu",
                )
            model_time = time.time() - model_start
            total_time = time.time() - start_time

            logger.info(
                f"标点恢复模型加载完成 - 模型创建耗时: {model_time:.2f}秒, 总耗时: {total_time:.2f}秒"
            )
            return True
        except Exception as e:
            logger.error(f"标点恢复模型加载失败: {str(e)}")
            return False

    def initialize(self):
        """并行初始化FunASR模型"""
        if self.initialized:
            return {"success": True, "message": "模型已初始化"}

        try:
            import threading
            import time

            logger.info("正在并行初始化FunASR模型...")
            start_time = time.time()

            # 创建加载结果存储
            results = {}

            def load_model_thread(model_name, load_func):
                """模型加载线程包装函数"""
                thread_start = time.time()
                results[model_name] = load_func()
                thread_time = time.time() - thread_start
                logger.info(f"{model_name}模型加载线程耗时: {thread_time:.2f}秒")

            # 创建并启动三个并行线程
            threads = [
                threading.Thread(
                    target=load_model_thread, args=("asr", self._load_asr_model)
                ),
                threading.Thread(
                    target=load_model_thread, args=("vad", self._load_vad_model)
                ),
                threading.Thread(
                    target=load_model_thread, args=("punc", self._load_punc_model)
                ),
            ]

            # 启动所有线程
            for thread in threads:
                thread.start()

            # 等待所有线程完成,设置超时
            for thread in threads:
                thread.join(timeout=300)  # 5分钟超时
                if thread.is_alive():
                    logger.error(f"模型加载线程超时")
                    return {
                        "success": False,
                        "error": "模型加载超时",
                        "type": "timeout_error",
                    }

            # 检查加载结果
            failed_models = [name for name, success in results.items() if not success]

            if failed_models:
                error_msg = f"以下模型加载失败: {', '.join(failed_models)}"
                logger.error(error_msg)
                return {"success": False, "error": error_msg, "type": "init_error"}

            total_time = time.time() - start_time
            self.initialized = True
            logger.info(
                f"所有FunASR模型并行初始化完成,总耗时: {total_time:.2f}秒"
            )
            return {
                "success": True,
                "message": f"FunASR模型并行初始化成功,耗时: {total_time:.2f}秒",
            }

        except ImportError as e:
            error_msg = "FunASR未安装,请先安装FunASR: pip install funasr"
            logger.error(error_msg)
            return {"success": False, "error": error_msg, "type": "import_error"}

        except Exception as e:
            error_msg = f"FunASR模型初始化失败: {str(e)}"
            logger.error(error_msg)
            logger.error(traceback.format_exc())
            return {"success": False, "error": error_msg, "type": "init_error"}

    def transcribe_audio(self, audio_path, options=None):
        """转录音频文件"""
        if not self.initialized:
            init_result = self.initialize()
            if not init_result["success"]:
                return init_result

        try:
            # 检查音频文件是否存在
            if not os.path.exists(audio_path):
                return {"success": False, "error": f"音频文件不存在: {audio_path}"}

            logger.info(f"开始转录音频文件: {audio_path}")

            # 设置默认选项
            default_options = {
                "batch_size_s": 60,
                "hotword": "",
                "use_vad": True,
                "use_punc": True,  # 使用FunASR自带的标点恢复
                "language": "zh",
            }

            if options:
                default_options.update(options)

            # 执行语音识别
            if default_options["use_vad"]:
                vad_result = self.vad_model.generate(
                    input=audio_path, batch_size_s=default_options["batch_size_s"]
                )
                logger.info("VAD处理完成")

            # 执行ASR识别
            asr_result = self.asr_model.generate(
                input=audio_path,
                batch_size_s=default_options["batch_size_s"],
                hotword=default_options["hotword"],
                cache={},
            )

            # 提取识别文本
            if isinstance(asr_result, list) and len(asr_result) > 0:
                if isinstance(asr_result[0], dict) and "text" in asr_result[0]:
                    raw_text = asr_result[0]["text"]
                else:
                    raw_text = str(asr_result[0])
            else:
                raw_text = str(asr_result)

            logger.info(f"ASR识别完成,原始文本: {raw_text[:100]}...")

            # 使用FunASR进行标点恢复
            final_text = raw_text
            if default_options["use_punc"] and self.punc_model and raw_text.strip():
                try:
                    punc_result = self.punc_model.generate(input=raw_text)
                    if isinstance(punc_result, list) and len(punc_result) > 0:
                        if (
                            isinstance(punc_result[0], dict)
                            and "text" in punc_result[0]
                        ):
                            final_text = punc_result[0]["text"]
                        else:
                            final_text = str(punc_result[0])
                    logger.info("FunASR标点恢复完成")
                except Exception as e:
                    logger.warning(f"FunASR标点恢复失败,使用原始文本: {str(e)}")

            duration = self._get_audio_duration(audio_path)
            self.transcription_count += 1

            result = {
                "success": True,
                "text": final_text,
                "raw_text": raw_text,
                "confidence": (
                    getattr(asr_result[0], "confidence", 0.0)
                    if isinstance(asr_result, list)
                    else 0.0
                ),
                "duration": duration,
                "language": "zh-CN",
                "model_type": "pytorch",  # 标识使用的是pytorch版本
            }

            # 生产环境:每10次转录后进行内存清理
            if self.transcription_count % 10 == 0:
                self._cleanup_memory()
                logger.info(f"已完成 {self.transcription_count} 次转录,执行内存清理")

            logger.info(f"转录完成,最终文本: {final_text[:100]}...")
            return result

        except Exception as e:
            error_msg = f"音频转录失败: {str(e)}"
            logger.error(error_msg)
            logger.error(traceback.format_exc())
            return {"success": False, "error": error_msg, "type": "transcription_error"}

    def _get_audio_duration(self, audio_path):
        """获取音频时长"""
        try:
            import librosa

            duration = librosa.get_duration(filename=audio_path)
            self.total_audio_duration += duration  # 累计音频时长
            return duration
        except:
            return 0.0

    def _cleanup_memory(self):
        """生产环境内存清理"""
        try:
            import gc

            gc.collect()
            logger.info("内存清理完成")
        except Exception as e:
            logger.warning(f"内存清理失败: {str(e)}")

    def get_performance_stats(self):
        """获取性能统计信息"""
        return {
            "transcription_count": self.transcription_count,
            "total_audio_duration": round(self.total_audio_duration, 2),
            "average_duration": round(
                self.total_audio_duration / max(1, self.transcription_count), 2
            ),
            "initialized": self.initialized,
            "models_loaded": {
                "asr": self.asr_model is not None,
                "vad": self.vad_model is not None,
                "punc": self.punc_model is not None,
            },
        }

    def check_status(self):
        """检查FunASR状态"""
        try:
            import funasr

            return {
                "success": True,
                "installed": True,
                "initialized": self.initialized,
                "version": getattr(funasr, "__version__", "unknown"),
                "models": {
                    "asr": self.asr_model is not None,
                    "vad": self.vad_model is not None,
                    "punc": self.punc_model is not None,  # FunASR标点恢复模型状态
                },
            }
        except ImportError:
            return {
                "success": False,
                "installed": False,
                "initialized": False,
                "error": "FunASR未安装",
            }

    def run(self):
        """运行服务器主循环"""
        logger.info("FunASR服务器启动")

        # 解析 damo 根目录
        def _default_damo_root():
            # 允许通过 MODELSCOPE_CACHE 指定根;常见是 ~/.cache/modelscope/hub/damo
            root = os.environ.get("MODELSCOPE_CACHE")
            if root:
                # 兼容两种布局:<cache>/damo 或 <cache>/hub/damo
                if os.path.isdir(os.path.join(root, "damo")):
                    return os.path.join(root, "damo")
                if os.path.isdir(os.path.join(root, "hub", "damo")):
                    return os.path.join(root, "hub", "damo")
                # 像 Node 一样自定义到 /Volumes/APFS/AI/models/damo,就直接传入 --damo-root
            # 默认回到用户主目录的 modelscope/hub/damo
            home_dir = os.path.expanduser("~")
            return os.path.join(home_dir, ".cache", "modelscope", "hub", "damo")

        cache_path = self.damo_root if self.damo_root else _default_damo_root()
        logger.info(f"使用的模型根目录(damo root): {cache_path}")

        repos = [
            "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
            "speech_fsmn_vad_zh-cn-16k-common-pytorch",
            "punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
        ]

        def _repo_ready(repo_dir):
            # 目录存在且包含任意常见权重/配置文件即认为已就绪
            if not os.path.isdir(repo_dir):
                return False
            patterns = [
                "model.pt", "pytorch_model.bin", "*.onnx",
                "config.json", "configuration.json", "model.yaml", "vocab*"
            ]
            for pat in patterns:
                if glob.glob(os.path.join(repo_dir, pat)):
                    return True
            return False

        missing = []
        for r in repos:
            rd = os.path.join(cache_path, r)
            if not _repo_ready(rd):
                missing.append(r)

        if not missing:
            logger.info("模型文件存在,开始初始化")
            init_result = self.initialize()
        else:
            logger.info(f"模型文件不存在或不完整:{', '.join(missing)},跳过初始化")
            init_result = {
                "success": False,
                "error": "模型文件未下载,请先下载模型",
                "type": "models_not_downloaded"
            }
        print(json.dumps(init_result, ensure_ascii=False))
        sys.stdout.flush()

        while self.running:
            try:
                # 读取命令
                line = sys.stdin.readline()
                if not line:
                    break

                line = line.strip()
                if not line:
                    continue

                try:
                    command = json.loads(line)
                except json.JSONDecodeError:
                    result = {"success": False, "error": "无效的JSON命令"}
                    print(json.dumps(result, ensure_ascii=False))
                    sys.stdout.flush()
                    continue

                # 处理命令
                if command.get("action") == "transcribe":
                    audio_path = command.get("audio_path")
                    options = command.get("options", {})
                    result = self.transcribe_audio(audio_path, options)
                elif command.get("action") == "status":
                    result = self.check_status()
                elif command.get("action") == "stats":
                    result = {"success": True, "stats": self.get_performance_stats()}
                elif command.get("action") == "cleanup":
                    self._cleanup_memory()
                    result = {"success": True, "message": "内存清理完成"}
                elif command.get("action") == "exit":
                    result = {"success": True, "message": "服务器退出"}
                    print(json.dumps(result, ensure_ascii=False))
                    sys.stdout.flush()
                    break
                else:
                    result = {
                        "success": False,
                        "error": f"未知命令: {command.get('action')}",
                    }

                # 输出结果
                print(json.dumps(result, ensure_ascii=False))
                sys.stdout.flush()

            except KeyboardInterrupt:
                break
            except Exception as e:
                error_result = {
                    "success": False,
                    "error": str(e),
                    "traceback": traceback.format_exc(),
                }
                print(json.dumps(error_result, ensure_ascii=False))
                sys.stdout.flush()

        logger.info("FunASR服务器退出")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--damo-root", type=str, default=None,
                        help="damo 模型根目录,例如 /Volumes/APFS/AI/models/damo")
    args = parser.parse_args()

    server = FunASRServer(damo_root=args.damo_root)
    server.run()
```

## /jsconfig.json

```json path="/jsconfig.json" 
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

## /main.js

```js path="/main.js" 
const { app, globalShortcut, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const { spawn } = require("child_process");

// 导入日志管理器
const LogManager = require("./src/helpers/logManager");

// 初始化日志管理器
const logger = new LogManager();

// 添加全局错误处理
process.on("uncaughtException", (error) => {
  logger.error("Uncaught Exception:", error);
  if (error.code === "EPIPE") {
    return;
  }
  logger.error("Error stack:", error.stack);
});

process.on("unhandledRejection", (reason, promise) => {
  logger.error("Unhandled Rejection at:", { promise, reason });
});

// 导入助手模块
const EnvironmentManager = require("./src/helpers/environment");
const WindowManager = require("./src/helpers/windowManager");
const DatabaseManager = require("./src/helpers/database");
const ClipboardManager = require("./src/helpers/clipboard");
const FunASRManager = require("./src/helpers/funasrManager");
const TrayManager = require("./src/helpers/tray");
const HotkeyManager = require("./src/helpers/hotkeyManager");
const IPCHandlers = require("./src/helpers/ipcHandlers");

// 设置生产环境PATH
function setupProductionPath() {
  logger.info('设置生产环境PATH', {
    platform: process.platform,
    nodeEnv: process.env.NODE_ENV,
    currentPath: process.env.PATH
  });

  if (process.platform === 'darwin' && process.env.NODE_ENV !== 'development') {
    const commonPaths = [
      '/usr/local/bin',
      '/opt/homebrew/bin',
      '/usr/bin',
      '/bin',
      '/usr/sbin',
      '/sbin',
      '/Library/Frameworks/Python.framework/Versions/3.12/bin',
      '/Library/Frameworks/Python.framework/Versions/3.11/bin',
      '/Library/Frameworks/Python.framework/Versions/3.10/bin',
      '/Library/Frameworks/Python.framework/Versions/3.9/bin',
      '/Library/Frameworks/Python.framework/Versions/3.8/bin',
      // 添加更多可能的Python路径
      '/opt/homebrew/opt/python@3.11/bin',
      '/opt/homebrew/opt/python@3.10/bin',
      '/opt/homebrew/opt/python@3.9/bin',
      '/usr/local/opt/python@3.11/bin',
      '/usr/local/opt/python@3.10/bin',
      '/usr/local/opt/python@3.9/bin'
    ];
    
    const currentPath = process.env.PATH || '';
    const pathsToAdd = commonPaths.filter(p => !currentPath.includes(p));
    
    if (pathsToAdd.length > 0) {
      const newPath = `${currentPath}:${pathsToAdd.join(':')}`;
      process.env.PATH = newPath;
      logger.info('PATH已更新', {
        添加的路径: pathsToAdd,
        新PATH: newPath
      });
    } else {
      logger.info('PATH无需更新,所有路径已存在');
    }
  } else if (process.platform === 'win32' && process.env.NODE_ENV !== 'development') {
    // Windows平台的Python路径设置
    const commonPaths = [
      'C:\\Python311\\Scripts',
      'C:\\Python311',
      'C:\\Python310\\Scripts',
      'C:\\Python310',
      'C:\\Python39\\Scripts',
      'C:\\Python39',
      'C:\\Users\\' + require('os').userInfo().username + '\\AppData\\Local\\Programs\\Python\\Python311\\Scripts',
      'C:\\Users\\' + require('os').userInfo().username + '\\AppData\\Local\\Programs\\Python\\Python311',
      'C:\\Users\\' + require('os').userInfo().username + '\\AppData\\Local\\Programs\\Python\\Python310\\Scripts',
      'C:\\Users\\' + require('os').userInfo().username + '\\AppData\\Local\\Programs\\Python\\Python310'
    ];
    
    const currentPath = process.env.PATH || '';
    const pathsToAdd = commonPaths.filter(p => !currentPath.includes(p));
    
    if (pathsToAdd.length > 0) {
      const newPath = `${currentPath};${pathsToAdd.join(';')}`;
      process.env.PATH = newPath;
      logger.info('Windows PATH已更新', {
        添加的路径: pathsToAdd,
        新PATH: newPath
      });
    }
  }
}

// 在初始化管理器之前设置PATH
setupProductionPath();

// 设置用户数据目录环境变量,供Python脚本使用
process.env.ELECTRON_USER_DATA = app.getPath('userData');
logger.info('设置用户数据目录环境变量', {
  ELECTRON_USER_DATA: process.env.ELECTRON_USER_DATA
});

// 初始化管理器
const environmentManager = new EnvironmentManager();
const windowManager = new WindowManager();
const databaseManager = new DatabaseManager();
const clipboardManager = new ClipboardManager(logger); // 传递logger实例
const funasrManager = new FunASRManager(logger); // 传递logger实例
const trayManager = new TrayManager();
const hotkeyManager = new HotkeyManager();

// 初始化数据库
const dataDirectory = environmentManager.ensureDataDirectory();
databaseManager.initialize(dataDirectory);

// 使用所有管理器初始化IPC处理器
const ipcHandlers = new IPCHandlers({
  environmentManager,
  databaseManager,
  clipboardManager,
  funasrManager,
  windowManager,
  hotkeyManager,
  logger, // 传递logger实例
});

// 主应用启动函数
async function startApp() {
  logger.info('应用启动开始', {
    nodeEnv: process.env.NODE_ENV,
    platform: process.platform,
    arch: process.arch,
    electronVersion: process.versions.electron,
    appVersion: app.getVersion()
  });

  // 注释掉 accessibility 支持 - 可能干扰文本插入
  // try {
  //   app.setAccessibilitySupportEnabled(true);
  //   logger.info('✅ 已启用 Electron accessibility 支持');
  // } catch (error) {
  //   logger.warn('⚠️ 启用 accessibility 支持失败:', error.message);
  // }

  // 记录系统信息
  logger.info('系统信息', logger.getSystemInfo());

  // 开发模式下添加小延迟让Vite正确启动
  if (process.env.NODE_ENV === "development") {
    logger.info('开发模式,等待Vite启动...');
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }

  // 确保macOS上dock可见
  if (process.platform === 'darwin' && app.dock) {
    app.dock.show();
    logger.info('macOS Dock已显示');
  }

  // 在启动时初始化FunASR管理器(不等待以避免阻塞)
  logger.info('开始初始化FunASR管理器...');
  funasrManager.initializeAtStartup().catch((err) => {
    logger.warn("FunASR在启动时不可用,这不是关键问题", err);
  });

  // 创建主窗口
  try {
    logger.info('创建主窗口...');
    await windowManager.createMainWindow();
    logger.info('主窗口创建成功');
  } catch (error) {
    logger.error("创建主窗口时出错:", error);
  }

  // 创建控制面板窗口
  try {
    logger.info('创建控制面板窗口...');
    await windowManager.createControlPanelWindow();
    logger.info('控制面板窗口创建成功');
  } catch (error) {
    logger.error("创建控制面板窗口时出错:", error);
  }

  // 设置托盘
  logger.info('设置系统托盘...');
  trayManager.setWindows(
    windowManager.mainWindow,
    windowManager.controlPanelWindow
  );
  trayManager.setCreateControlPanelCallback(() =>
    windowManager.createControlPanelWindow()
  );
  await trayManager.createTray();
  logger.info('系统托盘设置完成');

  logger.info('应用启动完成');
}

// 应用事件处理器
app.whenReady().then(() => {
  startApp();
});

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    windowManager.createMainWindow();
  }
});

app.on("will-quit", () => {
  globalShortcut.unregisterAll();
});

// 导出管理器供其他模块使用
module.exports = {
  environmentManager,
  windowManager,
  databaseManager,
  clipboardManager,
  funasrManager,
  trayManager,
  hotkeyManager,
  logger
};
```

## /package.json

```json path="/package.json" 
{
  "name": "ququ",
  "version": "1.0.0",
  "description": " 蛐蛐-基于FunASR和可配置AI模型的中文优化语音转文字应用",
  "main": "main.js",
  "private": true,
  "scripts": {
    "start": "electron .",
    "dev": "concurrently -k -r \"npm:dev:renderer\" \"npm:dev:main\"",
    "dev:main": "cross-env NODE_ENV=development electron . --dev",
    "dev:renderer": "cd src && vite",
    "build": "cd src && vite build && cd .. && electron-builder",
    "postinstall": "electron-builder install-app-deps",
    "build:renderer": "cd src && vite build",
    "prepare:python": "pip install funasr modelscope torch torchaudio librosa numpy && python download_models.py",
    "prepare:python:uv": "uv sync && uv run python download_models.py",
    "prepare:python:embedded": "node scripts/prepare-embedded-python.js",
    "prepare:python:info": "node scripts/prepare-embedded-python.js --info",
    "test:python": "node scripts/test-embedded-python.js",
    "test:python:info": "node scripts/test-embedded-python.js --info",
    "prebuild:mac": "npm run prepare:python:embedded && npm run build:renderer",
    "build:mac": "electron-builder --mac",
    "prebuild:win": "npm run prepare:python:embedded && npm run build:renderer",
    "build:win": "electron-builder --win",
    "prebuild:linux": "npm run prepare:python:embedded && npm run build:renderer",
    "build:linux": "electron-builder --linux",
    "pack": "npm run build:renderer && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --dir",
    "dist": "npm run prepare:python:embedded && npm run build:renderer && electron-builder",
    "lint": "cd src && eslint .",
    "preview": "cd src && vite preview",
    "clean": "node cleanup.js && rm -rf python"
  },
  "keywords": [
    "dictation",
    "speech-to-text",
    "funasr",
    "ai",
    "llm",
    "chinese",
    "electron",
    "desktop-app"
  ],
  "author": {
    "name": "yan5xu",
    "email": "lueco.x@gmail.com"
  },
  "license": "MIT",
  "devDependencies": {
    "@electron/notarize": "^3.0.1",
    "@eslint/js": "^9.25.0",
    "@tailwindcss/postcss": "^4.1.13",
    "@tailwindcss/vite": "^4.1.10",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.2",
    "@vitejs/plugin-react": "^4.4.1",
    "autoprefixer": "^10.4.21",
    "concurrently": "^8.2.2",
    "cross-env": "^10.0.0",
    "electron": "36.5.0",
    "electron-builder": "^24.6.4",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "node-gyp": "^11.4.2",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.10",
    "vite": "^6.3.5"
  },
  "dependencies": {
    "@radix-ui/react-dialog": "^1.1.14",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-select": "^2.2.5",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-tabs": "^1.1.12",
    "asynckit": "^0.4.0",
    "axios": "^1.6.0",
    "better-sqlite3": "11.10.0",
    "bindings": "^1.5.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "combined-stream": "^1.0.8",
    "delayed-stream": "^1.0.0",
    "dotenv": "^16.3.1",
    "electron-updater": "^6.6.2",
    "es-errors": "^1.3.0",
    "es-object-atoms": "^1.1.1",
    "es-set-tostringtag": "^2.1.0",
    "ffmpeg-static": "^5.2.0",
    "file-uri-to-path": "^2.0.0",
    "form-data": "^4.0.4",
    "get-intrinsic": "^1.3.0",
    "lucide-react": "^0.518.0",
    "math-intrinsics": "^1.1.0",
    "mime-db": "^1.54.0",
    "mime-types": "^3.0.1",
    "next-themes": "^0.4.6",
    "osascript": "^1.0.7",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "shadcn-ui": "^0.9.5",
    "sonner": "^2.0.7",
    "tailwind-merge": "^3.3.1",
    "tailwindcss-animate": "^1.0.7",
    "tar": "^7.4.3",
    "tw-animate-css": "^1.3.4",
    "unzipper": "^0.12.3"
  },
  "build": {
    "appId": "com.ququ.app",
    "productName": "蛐蛐",
    "directories": {
      "output": "dist"
    },
    "files": [
      "main.js",
      "preload.js",
      "funasr_server.py",
      "download_models.py",
      "package.json",
      "python/**/*",
      "node_modules/**/*",
      "src/dist/**/*",
      "src/helpers/**/*",
      "src/utils/**/*",
      "assets/**/*",
      "!node_modules/.cache/**/*",
      "!src/node_modules/**/*",
      "!python/**/*.pyc",
      "!python/**/__pycache__/**/*"
    ],
    "asarUnpack": [
      "funasr_server.py",
      "download_models.py",
      "python/**/*",
      "node_modules/**/*ffmpeg-static*/**/*",
      "node_modules/**/*better-sqlite3*/**/*",
      "node_modules/**/*bindings*/**/*"
    ],
    "mac": {
      "category": "public.app-category.productivity",
      "icon": "assets/icon.icns",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "entitlements.mac.plist",
      "entitlementsInherit": "entitlements.mac.plist",
      "notarize": {
        "teamId": "7MTHK6DGS4"
      }
    },
    "win": {
      "icon": "assets/icon.ico"
    },
    "linux": {
      "icon": "assets/icon.png",
      "category": "Office"
    }
  }
}
```

## /pnpm-workspace.yaml

```yaml path="/pnpm-workspace.yaml" 
onlyBuiltDependencies:
  - better-sqlite3
  - electron

```

## /postcss.config.js

```js path="/postcss.config.js" 
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}
```

## /preload.js

```js path="/preload.js" 
const { contextBridge, ipcRenderer } = require("electron");

// 暴露安全的API给渲染进程
contextBridge.exposeInMainWorld("electronAPI", {
  // 窗口控制
  hideWindow: () => ipcRenderer.invoke("hide-window"),
  showWindow: () => ipcRenderer.invoke("show-window"),
  minimizeWindow: () => ipcRenderer.invoke("minimize-window"),
  closeWindow: () => ipcRenderer.invoke("close-window"),

  // 录音相关
  startRecording: () => ipcRenderer.invoke("start-recording"),
  stopRecording: () => ipcRenderer.invoke("stop-recording"),
  onToggleDictation: (callback) => {
    ipcRenderer.on("toggle-dictation", callback);
    return () => ipcRenderer.removeListener("toggle-dictation", callback);
  },

  // FunASR语音识别
  transcribeAudio: (audioData) => ipcRenderer.invoke("transcribe-audio", audioData),
  checkFunASRStatus: () => ipcRenderer.invoke("check-funasr-status"),
  installFunASR: () => ipcRenderer.invoke("install-funasr"),
  restartFunasrServer: () => ipcRenderer.invoke("restart-funasr-server"),

  // 模型文件管理
  checkModelFiles: () => ipcRenderer.invoke("check-model-files"),
  getDownloadProgress: () => ipcRenderer.invoke("get-download-progress"),
  downloadModels: () => ipcRenderer.invoke("download-models"),

  // AI文本处理
  processText: (text, mode) => ipcRenderer.invoke("process-text", text, mode),
  checkAIStatus: (testConfig) => ipcRenderer.invoke("check-ai-status", testConfig),

  // 剪贴板操作
  pasteText: (text) => ipcRenderer.invoke("paste-text", text),
  copyText: (text) => ipcRenderer.invoke("copy-text", text),
  readClipboard: () => ipcRenderer.invoke("read-clipboard"),
  writeClipboard: (text) => ipcRenderer.invoke("write-clipboard", text),

  // 数据库操作
  saveTranscription: (text, processedText) => 
    ipcRenderer.invoke("save-transcription", text, processedText),
  getTranscriptions: (limit, offset) => 
    ipcRenderer.invoke("get-transcriptions", limit, offset),
  deleteTranscription: (id) => 
    ipcRenderer.invoke("delete-transcription", id),
  clearAllTranscriptions: () => 
    ipcRenderer.invoke("clear-all-transcriptions"),

  // 设置管理
  getSettings: () => ipcRenderer.invoke("get-settings"),
  getAllSettings: () => ipcRenderer.invoke("get-all-settings"),
  getSetting: (key, defaultValue) => ipcRenderer.invoke("get-setting", key, defaultValue),
  setSetting: (key, value) => ipcRenderer.invoke("set-setting", key, value),
  saveSetting: (key, value) => ipcRenderer.invoke("save-setting", key, value),
  resetSettings: () => ipcRenderer.invoke("reset-settings"),

  // 热键管理
  registerHotkey: (hotkey) => ipcRenderer.invoke("register-hotkey", hotkey),
  unregisterHotkey: (hotkey) => ipcRenderer.invoke("unregister-hotkey", hotkey),
  getCurrentHotkey: () => ipcRenderer.invoke("get-current-hotkey"),
  
  // F2热键管理
  registerF2Hotkey: () => ipcRenderer.invoke("register-f2-hotkey"),
  unregisterF2Hotkey: () => ipcRenderer.invoke("unregister-f2-hotkey"),
  setRecordingState: (isRecording) => ipcRenderer.invoke("set-recording-state", isRecording),
  getRecordingState: () => ipcRenderer.invoke("get-recording-state"),
  
  // F2双击事件监听
  onF2DoubleClick: (callback) => {
    ipcRenderer.on("f2-double-click", callback);
    return () => ipcRenderer.removeListener("f2-double-click", callback);
  },
  
  // 热键触发事件监听
  onHotkeyTriggered: (callback) => {
    ipcRenderer.on("hotkey-triggered", callback);
    return () => ipcRenderer.removeListener("hotkey-triggered", callback);
  },

  // 文件操作
  exportTranscriptions: (format) => ipcRenderer.invoke("export-transcriptions", format),
  importSettings: () => ipcRenderer.invoke("import-settings"),
  exportSettings: () => ipcRenderer.invoke("export-settings"),

  // 系统信息
  getSystemInfo: () => ipcRenderer.invoke("get-system-info"),
  checkPermissions: () => ipcRenderer.invoke("check-permissions"),
  requestPermissions: () => ipcRenderer.invoke("request-permissions"),
  testAccessibilityPermission: () => ipcRenderer.invoke("test-accessibility-permission"),
  openSystemPermissions: () => ipcRenderer.invoke("open-system-permissions"),

  // 应用信息
  getAppVersion: () => ipcRenderer.invoke("get-app-version"),
  checkForUpdates: () => ipcRenderer.invoke("check-for-updates"),

  // 调试和日志
  log: (level, message) => ipcRenderer.invoke("log", level, message),
  getDebugInfo: () => ipcRenderer.invoke("get-debug-info"),

  // 事件监听
  onTranscriptionUpdate: (callback) => {
    ipcRenderer.on("transcription-update", callback);
    return () => ipcRenderer.removeListener("transcription-update", callback);
  },
  onProcessingUpdate: (callback) => {
    ipcRenderer.on("processing-update", callback);
    return () => ipcRenderer.removeListener("processing-update", callback);
  },
  onError: (callback) => {
    ipcRenderer.on("error", callback);
    return () => ipcRenderer.removeListener("error", callback);
  },
  onSettingsUpdate: (callback) => {
    ipcRenderer.on("settings-update", callback);
    return () => ipcRenderer.removeListener("settings-update", callback);
  },

  // 控制面板相关
  openControlPanel: () => ipcRenderer.invoke("open-control-panel"),
  closeControlPanel: () => ipcRenderer.invoke("close-control-panel"),

  // 历史记录窗口相关
  openHistoryWindow: () => ipcRenderer.invoke("open-history-window"),
  closeHistoryWindow: () => ipcRenderer.invoke("close-history-window"),
  hideHistoryWindow: () => ipcRenderer.invoke("hide-history-window"),

  // 设置窗口相关
  openSettingsWindow: () => ipcRenderer.invoke("open-settings-window"),
  closeSettingsWindow: () => ipcRenderer.invoke("close-settings-window"),
  hideSettingsWindow: () => ipcRenderer.invoke("hide-settings-window"),

  // 中文特定功能
  detectLanguage: (text) => ipcRenderer.invoke("detect-language", text),
  segmentChinese: (text) => ipcRenderer.invoke("segment-chinese", text),
  addPunctuation: (text) => ipcRenderer.invoke("add-punctuation", text),

  // 音频处理
  convertAudioFormat: (audioData, targetFormat) => 
    ipcRenderer.invoke("convert-audio-format", audioData, targetFormat),
  enhanceAudio: (audioData) => ipcRenderer.invoke("enhance-audio", audioData),

  // 模型管理
  downloadModel: (modelName) => ipcRenderer.invoke("download-model", modelName),
  getAvailableModels: () => ipcRenderer.invoke("get-available-models"),
  getCurrentModel: () => ipcRenderer.invoke("get-current-model"),
  switchModel: (modelName) => ipcRenderer.invoke("switch-model", modelName),

  // 模型下载进度监听
  onModelDownloadProgress: (callback) => {
    ipcRenderer.on("model-download-progress", callback);
    return () => ipcRenderer.removeListener("model-download-progress", callback);
  },

  // 性能监控
  getPerformanceStats: () => ipcRenderer.invoke("get-performance-stats"),
  clearPerformanceStats: () => ipcRenderer.invoke("clear-performance-stats")
});

// 添加一些实用的常量
contextBridge.exposeInMainWorld("constants", {
  APP_NAME: "蛐蛐 (QuQu)",
  VERSION: "1.0.0",
  SUPPORTED_AUDIO_FORMATS: ["wav", "mp3", "m4a", "flac"],
  SUPPORTED_EXPORT_FORMATS: ["txt", "docx", "pdf", "json"],
  DEFAULT_HOTKEY: "CommandOrControl+Shift+Space",
  MAX_RECORDING_DURATION: 300000, // 5分钟
  MAX_TEXT_LENGTH: 10000,
  CHINESE_LANGUAGE_CODES: ["zh", "zh-CN", "zh-TW", "zh-HK"]
});

// 添加调试信息(仅在开发模式下)
if (process.env.NODE_ENV === "development") {
  contextBridge.exposeInMainWorld("debug", {
    getElectronVersion: () => process.versions.electron,
    getNodeVersion: () => process.versions.node,
    getChromeVersion: () => process.versions.chrome,
    getPlatform: () => process.platform,
    getArch: () => process.arch
  });
}
```

## /pyproject.toml

```toml path="/pyproject.toml" 
[project]
name = "ququ"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "funasr>=1.2.7",
    "librosa>=0.11.0",
    "numpy<2",
    "torch==2.0.1",
    "torchaudio==2.0.2",
    "torchvision==0.15.2",
]

```

## /scripts/prepare-embedded-python.js

```js path="/scripts/prepare-embedded-python.js" 
const fs = require('fs');
const path = require('path');
const https = require('https');
const { execSync } = require('child_process');
const { createWriteStream } = require('fs');
const { pipeline } = require('stream');
const { promisify } = require('util');
const tar = require('tar');

const pipelineAsync = promisify(pipeline);

class EmbeddedPythonBuilder {
  constructor() {
    this.pythonVersion = '3.11.6';
    this.buildDate = '20231002';
    this.pythonDir = path.join(__dirname, '..', 'python');
    this.forceReinstall = false;
  }

  async build() {
    console.log('🐍 开始准备嵌入式Python环境...');
    
    try {
      // 1. 检查现有环境是否完整(除非强制重新安装)
      if (!this.forceReinstall) {
        const existingInfo = await this.getEmbeddedPythonInfo();
        if (existingInfo && existingInfo.ready) {
          console.log('✅ 检测到现有的嵌入式Python环境:');
          console.log(`   版本: ${existingInfo.version}`);
          console.log(`   大小: ${existingInfo.size.mb}MB (${existingInfo.size.files} 个文件)`);
          
          // 验证关键依赖是否完整
          const pythonPath = path.join(this.pythonDir, 'bin', 'python3.11');
          const isValid = await this.validateExistingEnvironment(pythonPath);
          
          if (isValid) {
            console.log('✅ 现有环境验证通过,跳过重新安装');
            return;
          } else {
            console.log('⚠️ 现有环境不完整,将重新安装...');
          }
        } else {
          console.log('📋 未检测到现有环境或环境不可用,开始全新安装...');
        }
      } else {
        console.log('🔄 强制重新安装模式,跳过现有环境检查');
      }
      
      // 2. 清理现有Python目录
      await this.cleanup();
      
      // 3. 下载Python运行时
      await this.downloadPythonRuntime();
      
      // 4. 安装Python依赖
      await this.installDependencies();
      
      // 5. 清理不必要文件
      await this.cleanupUnnecessaryFiles();
      
      console.log('✅ 嵌入式Python环境准备完成!');
      
    } catch (error) {
      console.error('❌ 准备Python环境失败:', error.message);
      process.exit(1);
    }
  }

  async cleanup() {
    if (fs.existsSync(this.pythonDir)) {
      console.log('🧹 清理现有Python目录...');
      fs.rmSync(this.pythonDir, { recursive: true, force: true });
    }
    fs.mkdirSync(this.pythonDir, { recursive: true });
  }

  async downloadPythonRuntime() {
    const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64';
    const filename = `cpython-${this.pythonVersion}+${this.buildDate}-${arch}-apple-darwin-install_only.tar.gz`;
    const url = `https://github.com/indygreg/python-build-standalone/releases/download/${this.buildDate}/${filename}`;
    const tarPath = path.join(this.pythonDir, 'python.tar.gz');

    console.log(`📥 下载Python运行时 (${arch})...`);
    console.log(`URL: ${url}`);

    await this.downloadFile(url, tarPath);
    
    console.log('📦 解压Python运行时...');
    await tar.extract({
      file: tarPath,
      cwd: this.pythonDir,
      strip: 1
    });

    // 删除压缩包
    fs.unlinkSync(tarPath);
    
    console.log('✅ Python运行时下载完成');
  }

  async downloadFile(url, outputPath) {
    return new Promise((resolve, reject) => {
      const file = createWriteStream(outputPath);
      
      https.get(url, (response) => {
        if (response.statusCode === 302 || response.statusCode === 301) {
          // 处理重定向
          return this.downloadFile(response.headers.location, outputPath)
            .then(resolve)
            .catch(reject);
        }
        
        if (response.statusCode !== 200) {
          reject(new Error(`下载失败: HTTP ${response.statusCode}`));
          return;
        }

        const totalSize = parseInt(response.headers['content-length'], 10);
        let downloadedSize = 0;

        response.on('data', (chunk) => {
          downloadedSize += chunk.length;
          if (totalSize) {
            const progress = Math.round((downloadedSize / totalSize) * 100);
            process.stdout.write(`\r进度: ${progress}% (${Math.round(downloadedSize / 1024 / 1024)}MB / ${Math.round(totalSize / 1024 / 1024)}MB)`);
          }
        });

        response.pipe(file);

        file.on('finish', () => {
          file.close();
          console.log('\n✅ 下载完成');
          resolve();
        });

        file.on('error', (error) => {
          fs.unlink(outputPath, () => {}); // 错误时清理
          reject(error);
        });

      }).on('error', (error) => {
        reject(error);
      });
    });
  }

  async installDependencies() {
    const pythonPath = path.join(this.pythonDir, 'bin', 'python3.11');
    const sitePackagesPath = path.join(this.pythonDir, 'lib', 'python3.11', 'site-packages');

    console.log('📦 安装Python依赖...');

    // 确保pip是最新的
    console.log('⬆️ 升级pip...');
    try {
      execSync(`"${pythonPath}" -m pip install --upgrade pip`, {
        stdio: 'inherit',
        env: {
          ...process.env,
          PYTHONHOME: this.pythonDir,
          PYTHONPATH: sitePackagesPath,
          PYTHONDONTWRITEBYTECODE: '1'
        }
      });
    } catch (error) {
      console.warn('⚠️ pip升级失败,继续安装依赖...');
    }

    // 定义依赖列表 - 确保numpy等核心依赖被正确安装
    const dependencies = [
      'numpy<2',  // 先安装numpy,作为其他库的基础依赖
      'torch==2.0.1',
      'torchaudio==2.0.2',
      'torchvision==0.15.2',
      'librosa>=0.11.0',
      'funasr>=1.2.7'
    ];

    // 逐个安装依赖(包含所有子依赖)
    for (const dep of dependencies) {
      console.log(`📦 安装 ${dep}...`);
      try {
        // 构建完整的环境变量
        const installEnv = {
          ...process.env,
          PYTHONHOME: this.pythonDir,
          PYTHONPATH: sitePackagesPath,
          PYTHONDONTWRITEBYTECODE: '1',
          PYTHONIOENCODING: 'utf-8',
          PYTHONUNBUFFERED: '1',
          PIP_NO_CACHE_DIR: '1',
          // 确保库路径正确
          LD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
          DYLD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'), // macOS
        };
        
        // 清除可能干扰的环境变量
        delete installEnv.PYTHONUSERBASE;
        delete installEnv.PYTHONSTARTUP;
        delete installEnv.VIRTUAL_ENV;
        
        execSync(`"${pythonPath}" -m pip install --target "${sitePackagesPath}" --no-deps --force-reinstall "${dep}"`, {
          stdio: 'inherit',
          env: installEnv
        });
        
        // 安装依赖的依赖
        execSync(`"${pythonPath}" -m pip install --target "${sitePackagesPath}" --only-binary=all "${dep}"`, {
          stdio: 'inherit',
          env: installEnv
        });
        
        console.log(`✅ ${dep} 安装完成`);
      } catch (error) {
        console.error(`❌ ${dep} 安装失败:`, error.message);
        // 尝试不使用 --no-deps 重新安装
        try {
          console.log(`🔄 重试安装 ${dep} (包含依赖)...`);
          const installEnv = {
            ...process.env,
            PYTHONHOME: this.pythonDir,
            PYTHONPATH: sitePackagesPath,
            PYTHONDONTWRITEBYTECODE: '1',
            PYTHONIOENCODING: 'utf-8',
            PYTHONUNBUFFERED: '1',
            PIP_NO_CACHE_DIR: '1',
            LD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
            DYLD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
          };
          
          delete installEnv.PYTHONUSERBASE;
          delete installEnv.PYTHONSTARTUP;
          delete installEnv.VIRTUAL_ENV;
          
          execSync(`"${pythonPath}" -m pip install --target "${sitePackagesPath}" --force-reinstall "${dep}"`, {
            stdio: 'inherit',
            env: installEnv
          });
          console.log(`✅ ${dep} 重试安装成功`);
        } catch (retryError) {
          console.error(`❌ ${dep} 重试安装也失败:`, retryError.message);
          // 继续安装其他依赖
        }
      }
    }

    // 验证关键依赖
    await this.verifyDependencies(pythonPath);
  }

  async verifyDependencies(pythonPath) {
    console.log('🔍 验证依赖安装...');
    
    const criticalDeps = ['numpy', 'torch', 'librosa', 'funasr'];
    const sitePackagesPath = path.join(this.pythonDir, 'lib', 'python3.11', 'site-packages');
    
    for (const dep of criticalDeps) {
      try {
        // 构建完整的环境变量
        const verifyEnv = {
          ...process.env,
          PYTHONHOME: this.pythonDir,
          PYTHONPATH: sitePackagesPath,
          PYTHONDONTWRITEBYTECODE: '1',
          PYTHONIOENCODING: 'utf-8',
          PYTHONUNBUFFERED: '1',
          // 确保库路径正确
          LD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
          DYLD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'), // macOS
        };
        
        // 清除可能干扰的环境变量
        delete verifyEnv.PYTHONUSERBASE;
        delete verifyEnv.PYTHONSTARTUP;
        delete verifyEnv.VIRTUAL_ENV;
        
        const result = execSync(`"${pythonPath}" -c "import ${dep}; print('${dep} OK')"`, {
          stdio: 'pipe',
          env: verifyEnv
        });
        
        console.log(`✅ ${dep} 验证通过: ${result.toString().trim()}`);
      } catch (error) {
        console.error(`❌ ${dep} 验证失败:`, error.message);
        console.error('错误输出:', error.stderr?.toString() || '无');
        throw new Error(`关键依赖 ${dep} 安装失败: ${error.message}`);
      }
    }
  }

  async validateExistingEnvironment(pythonPath) {
    console.log('🔍 验证现有环境完整性...');
    
    try {
      // 检查Python可执行文件是否存在
      if (!fs.existsSync(pythonPath)) {
        console.log('❌ Python可执行文件不存在');
        return false;
      }
      
      // 检查关键依赖是否可用
      const criticalDeps = ['numpy', 'torch', 'librosa', 'funasr'];
      const sitePackagesPath = path.join(this.pythonDir, 'lib', 'python3.11', 'site-packages');
      
      // 构建环境变量
      const verifyEnv = {
        ...process.env,
        PYTHONHOME: this.pythonDir,
        PYTHONPATH: sitePackagesPath,
        PYTHONDONTWRITEBYTECODE: '1',
        PYTHONIOENCODING: 'utf-8',
        PYTHONUNBUFFERED: '1',
        LD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
        DYLD_LIBRARY_PATH: path.join(this.pythonDir, 'lib'),
      };
      
      // 清除可能干扰的环境变量
      delete verifyEnv.PYTHONUSERBASE;
      delete verifyEnv.PYTHONSTARTUP;
      delete verifyEnv.VIRTUAL_ENV;
      
      for (const dep of criticalDeps) {
        try {
          execSync(`"${pythonPath}" -c "import ${dep}; print('${dep} OK')"`, {
            stdio: 'pipe',
            env: verifyEnv,
            timeout: 10000 // 10秒超时
          });
          console.log(`✅ ${dep} 可用`);
        } catch (error) {
          console.log(`❌ ${dep} 不可用: ${error.message}`);
          return false;
        }
      }
      
      console.log('✅ 现有环境验证完成,所有关键依赖都可用');
      return true;
      
    } catch (error) {
      console.log(`❌ 环境验证失败: ${error.message}`);
      return false;
    }
  }

  async cleanupUnnecessaryFiles() {
    console.log('🧹 清理不必要文件...');
    
    const unnecessaryPaths = [
      path.join(this.pythonDir, 'share', 'doc'),
      path.join(this.pythonDir, 'share', 'man'),
      path.join(this.pythonDir, 'include'),
      path.join(this.pythonDir, 'lib', 'pkgconfig'),
      path.join(this.pythonDir, 'lib', 'python3.11', 'test'),
      path.join(this.pythonDir, 'lib', 'python3.11', 'distutils'),
    ];

    for (const unnecessaryPath of unnecessaryPaths) {
      if (fs.existsSync(unnecessaryPath)) {
        try {
          fs.rmSync(unnecessaryPath, { recursive: true, force: true });
          console.log(`🗑️ 删除: ${path.relative(this.pythonDir, unnecessaryPath)}`);
        } catch (error) {
          console.warn(`⚠️ 无法删除: ${unnecessaryPath}`);
        }
      }
    }

    // 删除.pyc文件
    this.deletePycFiles(this.pythonDir);
    
    console.log('✅ 清理完成');
  }

  deletePycFiles(dir) {
    const items = fs.readdirSync(dir);
    
    for (const item of items) {
      const fullPath = path.join(dir, item);
      const stat = fs.statSync(fullPath);
      
      if (stat.isDirectory()) {
        if (item === '__pycache__') {
          fs.rmSync(fullPath, { recursive: true, force: true });
        } else {
          this.deletePycFiles(fullPath);
        }
      } else if (item.endsWith('.pyc')) {
        fs.unlinkSync(fullPath);
      }
    }
  }

  async getEmbeddedPythonInfo() {
    const pythonPath = path.join(this.pythonDir, 'bin', 'python3.11');
    
    if (!fs.existsSync(pythonPath)) {
      return null;
    }

    try {
      const version = execSync(`"${pythonPath}" --version`, { 
        encoding: 'utf8',
        env: {
          ...process.env,
          PYTHONHOME: this.pythonDir,
          PYTHONDONTWRITEBYTECODE: '1'
        }
      }).trim();
      
      const sizeInfo = this.getDirectorySize(this.pythonDir);
      
      return {
        version,
        path: pythonPath,
        size: sizeInfo,
        ready: true
      };
    } catch (error) {
      return {
        ready: false,
        error: error.message
      };
    }
  }

  getDirectorySize(dirPath) {
    let totalSize = 0;
    let fileCount = 0;

    const calculateSize = (dir) => {
      const items = fs.readdirSync(dir);
      
      for (const item of items) {
        const fullPath = path.join(dir, item);
        const stat = fs.statSync(fullPath);
        
        if (stat.isDirectory()) {
          calculateSize(fullPath);
        } else {
          totalSize += stat.size;
          fileCount++;
        }
      }
    };

    calculateSize(dirPath);
    
    return {
      bytes: totalSize,
      mb: Math.round(totalSize / 1024 / 1024),
      files: fileCount
    };
  }
}

// 主函数
async function main() {
  const builder = new EmbeddedPythonBuilder();
  
  if (process.argv.includes('--info')) {
    const info = await builder.getEmbeddedPythonInfo();
    console.log('嵌入式Python信息:', JSON.stringify(info, null, 2));
    return;
  }
  
  // 检查是否强制重新安装
  if (process.argv.includes('--force')) {
    console.log('🔄 强制重新安装模式');
    builder.forceReinstall = true;
  }
  
  await builder.build();
  
  // 显示最终信息
  const info = await builder.getEmbeddedPythonInfo();
  console.log('\n📊 嵌入式Python环境信息:');
  console.log(`版本: ${info.version}`);
  console.log(`路径: ${info.path}`);
  console.log(`大小: ${info.size.mb}MB (${info.size.files} 个文件)`);
}

if (require.main === module) {
  main().catch(console.error);
}

module.exports = EmbeddedPythonBuilder;
```

## /scripts/test-embedded-python.js

```js path="/scripts/test-embedded-python.js" 
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

class EmbeddedPythonTester {
  constructor() {
    this.pythonDir = path.join(__dirname, '..', 'python');
    this.pythonPath = path.join(this.pythonDir, 'bin', 'python3.11');
  }

  async runTests() {
    console.log('🧪 开始测试嵌入式Python环境...\n');

    try {
      // 1. 检查Python可执行文件
      await this.testPythonExecutable();
      
      // 2. 检查Python版本
      await this.testPythonVersion();
      
      // 3. 检查关键依赖
      await this.testDependencies();
      
      // 4. 测试FunASR导入
      await this.testFunASRImport();
      
      // 5. 测试环境隔离
      await this.testEnvironmentIsolation();
      
      console.log('\n✅ 所有测试通过!嵌入式Python环境工作正常。');
      
    } catch (error) {
      console.error('\n❌ 测试失败:', error.message);
      process.exit(1);
    }
  }

  async testPythonExecutable() {
    console.log('1️⃣ 检查Python可执行文件...');
    
    if (!fs.existsSync(this.pythonPath)) {
      throw new Error(`Python可执行文件不存在: ${this.pythonPath}`);
    }
    
    const stats = fs.statSync(this.pythonPath);
    if (!stats.isFile()) {
      throw new Error('Python路径不是文件');
    }
    
    // 检查执行权限
    try {
      fs.accessSync(this.pythonPath, fs.constants.X_OK);
    } catch (error) {
      throw new Error('Python文件没有执行权限');
    }
    
    console.log('   ✅ Python可执行文件存在且有执行权限');
  }

  async testPythonVersion() {
    console.log('2️⃣ 检查Python版本...');
    
    const version = await this.runPythonCommand(['--version']);
    console.log(`   ✅ Python版本: ${version.trim()}`);
    
    if (!version.includes('Python 3.11')) {
      throw new Error(`期望Python 3.11,实际: ${version}`);
    }
  }

  async testDependencies() {
    console.log('3️⃣ 检查关键依赖...');
    
    const dependencies = [
      'sys',
      'os', 
      'json',
      'numpy',
      'torch',
      'librosa'
    ];
    
    for (const dep of dependencies) {
      try {
        await this.runPythonCommand(['-c', `import ${dep}; print("${dep} OK")`]);
        console.log(`   ✅ ${dep} 导入成功`);
      } catch (error) {
        throw new Error(`依赖 ${dep} 导入失败: ${error.message}`);
      }
    }
  }

  async testFunASRImport() {
    console.log('4️⃣ 测试FunASR导入...');
    
    try {
      const result = await this.runPythonCommand([
        '-c', 
        'import funasr; print("FunASR version:", getattr(funasr, "__version__", "unknown"))'
      ]);
      console.log(`   ✅ FunASR导入成功: ${result.trim()}`);
    } catch (error) {
      throw new Error(`FunASR导入失败: ${error.message}`);
    }
  }

  async testEnvironmentIsolation() {
    console.log('5️⃣ 测试环境隔离...');
    
    // 测试Python路径隔离
    const pythonPath = await this.runPythonCommand([
      '-c', 
      'import sys; print("\\n".join(sys.path))'
    ]);
    
    const paths = pythonPath.split('\n').filter(p => p.trim());
    const embeddedPaths = paths.filter(p => p.includes(this.pythonDir));
    
    if (embeddedPaths.length === 0) {
      throw new Error('Python路径中没有找到嵌入式Python目录');
    }
    
    console.log('   ✅ Python路径正确指向嵌入式环境');
    console.log(`   📁 嵌入式路径数量: ${embeddedPaths.length}`);
    
    // 测试site-packages路径
    const sitePackages = await this.runPythonCommand([
      '-c',
      'import site; print("\\n".join(site.getsitepackages()))'
    ]);
    
    if (!sitePackages.includes(this.pythonDir)) {
      throw new Error('site-packages路径不在嵌入式Python目录中');
    }
    
    console.log('   ✅ site-packages路径正确');
  }

  async runPythonCommand(args) {
    return new Promise((resolve, reject) => {
      // 设置隔离环境变量
      const env = {
        ...process.env,
        PYTHONHOME: this.pythonDir,
        PYTHONPATH: path.join(this.pythonDir, 'lib', 'python3.11', 'site-packages'),
        PYTHONDONTWRITEBYTECODE: '1',
        PYTHONIOENCODING: 'utf-8',
        PYTHONUNBUFFERED: '1'
      };
      
      // 清除可能干扰的环境变量
      delete env.PYTHONUSERBASE;
      delete env.PYTHONSTARTUP;
      delete env.VIRTUAL_ENV;
      
      const pythonProcess = spawn(this.pythonPath, args, {
        stdio: ['pipe', 'pipe', 'pipe'],
        env: env
      });
      
      let stdout = '';
      let stderr = '';
      
      pythonProcess.stdout.on('data', (data) => {
        stdout += data.toString();
      });
      
      pythonProcess.stderr.on('data', (data) => {
        stderr += data.toString();
      });
      
      pythonProcess.on('close', (code) => {
        if (code === 0) {
          resolve(stdout);
        } else {
          reject(new Error(`命令失败 (退出码: ${code}): ${stderr || stdout}`));
        }
      });
      
      pythonProcess.on('error', (error) => {
        reject(new Error(`进程错误: ${error.message}`));
      });
    });
  }

  async getEnvironmentInfo() {
    console.log('📊 嵌入式Python环境信息:\n');
    
    try {
      // Python版本
      const version = await this.runPythonCommand(['--version']);
      console.log(`Python版本: ${version.trim()}`);
      
      // Python路径
      console.log(`Python路径: ${this.pythonPath}`);
      
      // 环境大小
      const size = this.getDirectorySize(this.pythonDir);
      console.log(`环境大小: ${size.mb}MB (${size.files} 个文件)`);
      
      // 已安装包列表
      try {
        const packages = await this.runPythonCommand(['-m', 'pip', 'list', '--format=freeze']);
        const packageList = packages.split('\n').filter(p => p.trim()).length;
        console.log(`已安装包数量: ${packageList}`);
      } catch (error) {
        console.log('无法获取包列表');
      }
      
      // Python路径
      const pythonPaths = await this.runPythonCommand([
        '-c', 
        'import sys; print("\\n".join(sys.path))'
      ]);
      console.log('\nPython搜索路径:');
      pythonPaths.split('\n').forEach(p => {
        if (p.trim()) {
          console.log(`  ${p.trim()}`);
        }
      });
      
    } catch (error) {
      console.error('获取环境信息失败:', error.message);
    }
  }

  getDirectorySize(dirPath) {
    let totalSize = 0;
    let fileCount = 0;

    const calculateSize = (dir) => {
      try {
        const items = fs.readdirSync(dir);
        
        for (const item of items) {
          const fullPath = path.join(dir, item);
          const stat = fs.statSync(fullPath);
          
          if (stat.isDirectory()) {
            calculateSize(fullPath);
          } else {
            totalSize += stat.size;
            fileCount++;
          }
        }
      } catch (error) {
        // 忽略权限错误等
      }
    };

    calculateSize(dirPath);
    
    return {
      bytes: totalSize,
      mb: Math.round(totalSize / 1024 / 1024),
      files: fileCount
    };
  }
}

// 主函数
async function main() {
  const tester = new EmbeddedPythonTester();
  
  if (process.argv.includes('--info')) {
    await tester.getEnvironmentInfo();
    return;
  }
  
  await tester.runTests();
}

if (require.main === module) {
  main().catch(console.error);
}

module.exports = EmbeddedPythonTester;
```

## /src/App.jsx

```jsx path="/src/App.jsx" 
import React, { useState, useEffect, useRef, useCallback } from "react";
import "./index.css";
import { toast } from "sonner";
import { LoadingDots } from "./components/ui/loading-dots";
import { useHotkey } from "./hooks/useHotkey";
import { useWindowDrag } from "./hooks/useWindowDrag";
import { useRecording } from "./hooks/useRecording";
import { useTextProcessing } from "./hooks/useTextProcessing";
import { useModelStatus } from "./hooks/useModelStatus";
import { usePermissions } from "./hooks/usePermissions";
import { Mic, MicOff, Settings, History, Copy, Download } from "lucide-react";
import SettingsPanel from "./components/SettingsPanel";
import { ModelDownloadProgress } from "./components/ui/model-status-indicator";

// 动态导入设置页面组件
const SettingsPage = React.lazy(() => import('./settings.jsx').then(module => ({ default: module.SettingsPage })));

// 声波图标组件(空闲/悬停状态)
const SoundWaveIcon = ({ size = 16, isActive = false }) => {
  return (
    <div className="flex items-center justify-center gap-1">
      {[...Array(4)].map((_, i) => (
        <div
          key={i}
          className={`bg-slate-600 dark:bg-gray-300 rounded-full transition-all duration-150 shadow-sm ${
            isActive ? "wave-bar" : ""
          }`}
          style={{
            width: size * 0.15,
            height: isActive ? size * 0.8 : size * 0.4,
            animationDelay: isActive ? `${i * 0.1}s` : "0s",
          }}
        />
      ))}
    </div>
  );
};

// 加载指示器组件(FunASR启动中)
const LoadingIndicator = ({ size = 20 }) => {
  return (
    <div className="flex items-center justify-center gap-0.5">
      {[...Array(3)].map((_, i) => (
        <div
          key={i}
          className="w-1 bg-gray-500 rounded-full"
          style={{
            height: size * 0.6,
            animation: `loading-dots 1.4s ease-in-out infinite`,
            animationDelay: `${i * 0.2}s`,
          }}
        />
      ))}
    </div>
  );
};

// 语音波形指示器组件(处理状态)
const VoiceWaveIndicator = ({ isListening }) => {
  return (
    <div className="flex items-center justify-center gap-0.5">
      {[...Array(4)].map((_, i) => (
        <div
          key={i}
          className={`w-0.5 bg-white rounded-full transition-all duration-150 drop-shadow-sm ${
            isListening ? "animate-pulse h-5" : "h-2"
          }`}
          style={{
            animationDelay: isListening ? `${i * 0.1}s` : "0s",
            animationDuration: isListening ? `${0.6 + i * 0.1}s` : "0s",
          }}
        />
      ))}
    </div>
  );
};

// 增强的工具提示组件
const Tooltip = ({ children, content, position = "top" }) => {
  const [isVisible, setIsVisible] = useState(false);

  const getPositionClasses = () => {
    if (position === "bottom") {
      return {
        tooltip: "absolute top-full left-1/2 transform -translate-x-1/2 mt-2 px-2 py-1 text-white bg-gradient-to-r from-neutral-800 to-neutral-700 rounded-md whitespace-nowrap z-50 transition-opacity duration-150",
        arrow: "absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-b-2 border-transparent border-b-neutral-800"
      };
    }
    // 默认为顶部
    return {
      tooltip: "absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 text-white bg-gradient-to-r from-neutral-800 to-neutral-700 rounded-md whitespace-nowrap z-50 transition-opacity duration-150",
      arrow: "absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-t-2 border-transparent border-t-neutral-800"
    };
  };

  const { tooltip, arrow } = getPositionClasses();

  return (
    <div className="relative inline-block">
      <div
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </div>
      {isVisible && (
        <div
          className={tooltip}
          style={{ fontSize: "10px" }}
        >
          {content}
          <div className={arrow}></div>
        </div>
      )}
    </div>
  );
};

// 文本显示区域组件
const TextDisplay = ({ originalText, processedText, isProcessing, onCopy, onExport, onPaste }) => {
  if (!originalText && !processedText) {
    return null; // 当没有文本时不显示任何内容,避免重复
  }

  return (
    <div className="space-y-4">
      {/* 原始识别文本 - 简化设计,单行显示 */}
      {originalText && (
        <div className="bg-slate-100/80 dark:bg-gray-800/80 rounded-lg p-3 shadow-sm">
          <div className="flex items-center justify-between">
            <p className="chinese-content text-gray-800 dark:text-gray-200 flex-1 truncate pr-2">
              {originalText}
            </p>
            <button
              onClick={() => onCopy(originalText)}
              className="p-1.5 hover:bg-slate-200/70 dark:hover:bg-gray-700/70 rounded-md transition-colors flex-shrink-0"
              title="复制识别文本"
            >
              <Copy className="w-4 h-4 text-slate-600 dark:text-gray-400" />
            </button>
          </div>
        </div>
      )}

      {/* AI处理后文本 */}
      {(processedText || isProcessing) && (
        <div className="bg-gradient-to-br from-emerald-50 to-emerald-100 dark:from-emerald-900/20 dark:to-emerald-800/20 rounded-xl p-5 border-l-4 border-emerald-400 dark:border-emerald-500 shadow-lg border border-emerald-200/50 dark:border-emerald-700/50">
          <div className="flex items-center justify-between mb-3">
            <h3 className="text-base font-semibold chinese-title text-emerald-700 dark:text-emerald-400">AI优化后</h3>
            <div className="flex space-x-2">
              {processedText && (
                <>
                  <button
                    onClick={() => onPaste(processedText)}
                    className="p-2 hover:bg-emerald-200/70 dark:hover:bg-emerald-700/30 rounded-lg transition-colors shadow-sm"
                    title="粘贴优化文本"
                  >
                    <svg className="w-5 h-5 text-emerald-600 dark:text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
                    </svg>
                  </button>
                  <button
                    onClick={() => onCopy(processedText)}
                    className="p-2 hover:bg-emerald-200/70 dark:hover:bg-emerald-700/30 rounded-lg transition-colors shadow-sm"
                    title="复制优化文本"
                  >
                    <Copy className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
                  </button>
                  <button
                    onClick={() => onExport(processedText)}
                    className="p-2 hover:bg-emerald-200/70 dark:hover:bg-emerald-700/30 rounded-lg transition-colors shadow-sm"
                    title="导出文本"
                  >
                    <Download className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
                  </button>
                </>
              )}
            </div>
          </div>
          {isProcessing ? (
            <div className="flex items-center space-x-3 text-emerald-700 dark:text-emerald-400">
              <LoadingDots />
              <span className="status-text">AI正在优化文本...</span>
            </div>
          ) : (
            <p className="chinese-content leading-loose fade-in text-gray-800 dark:text-gray-200">
              {processedText}
            </p>
          )}
        </div>
      )}
    </div>
  );
};

export default function App() {
  // 检查URL参数来决定渲染哪个页面
  const urlParams = new URLSearchParams(window.location.search);
  const page = urlParams.get('page');
  
  // 如果是设置页面,直接渲染设置组件
  if (page === 'settings') {
    return (
      <React.Suspense fallback={
        <div className="min-h-screen bg-gradient-to-br from-slate-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center">
          <div className="flex items-center space-x-3">
            <LoadingDots />
            <span className="text-gray-700 dark:text-gray-300">加载设置页面...</span>
          </div>
        </div>
      }>
        <SettingsPage />
      </React.Suspense>
    );
  }

  const [isHovered, setIsHovered] = useState(false);
  const [originalText, setOriginalText] = useState("");
  const [processedText, setProcessedText] = useState("");
  const [showTextArea, setShowTextArea] = useState(false);
  const [showSettings, setShowSettings] = useState(false);
  
  const { isDragging, handleMouseDown, handleMouseMove, handleMouseUp, handleClick } = useWindowDrag();
  const modelStatus = useModelStatus();
  
  const {
    isRecording,
    isProcessing: isRecordingProcessing,
    isOptimizing,
    startRecording,
    stopRecording,
    error: recordingError
  } = useRecording();
  
  const {
    processText,
    isProcessing: isTextProcessing,
    error: textProcessingError
  } = useTextProcessing();

  // 防重复粘贴的引用
  const lastPasteRef = useRef({ text: '', timestamp: 0 });
  const PASTE_DEBOUNCE_TIME = 1000; // 1秒内相同文本不重复粘贴

  // 安全粘贴函数
  const safePaste = useCallback(async (text) => {
    const now = Date.now();
    const lastPaste = lastPasteRef.current;
    
    // 防重复粘贴:如果是相同文本且在防抖时间内,则跳过
    if (lastPaste.text === text && (now - lastPaste.timestamp) < PASTE_DEBOUNCE_TIME) {
      console.log("🚫 跳过重复粘贴,文本:", text.substring(0, 50) + "...");
      return;
    }
    
    // 更新最后粘贴记录
    lastPasteRef.current = { text, timestamp: now };
    
    console.log("🔄 safePaste 被调用,文本:", text.substring(0, 50) + "...");
    try {
      if (window.electronAPI) {
        console.log("📱 使用 Electron API 进行粘贴");
        await window.electronAPI.pasteText(text);
        console.log("✅ 粘贴成功");
        toast.success("文本已自动粘贴到当前输入框");
      } else {
        // Web环境下只能复制到剪贴板
        console.log("🌐 Web环境,仅复制到剪贴板");
        await navigator.clipboard.writeText(text);
        toast.info("文本已复制到剪贴板,请手动粘贴");
      }
    } catch (error) {
      console.error("❌ 粘贴文本失败:", error);
      toast.error("粘贴失败", {
        description: "请检查辅助功能权限。文本已复制到剪贴板 - 请手动使用 Cmd+V 粘贴。"
      });
    }
  }, []);

  // 处理录音完成(FunASR识别完成)
  const handleRecordingComplete = useCallback(async (transcriptionResult) => {
    console.log("🎤 handleRecordingComplete 被调用:", transcriptionResult);
    if (transcriptionResult.success && transcriptionResult.text) {
      console.log("✅ 转录成功,文本:", transcriptionResult.text);
      // 立即显示FunASR识别的原始文本
      setOriginalText(transcriptionResult.text);
      setShowTextArea(true);
      
      // 清空之前的处理结果,等待AI优化
      setProcessedText("");

      // 不立即粘贴,等待AI优化完成后再粘贴
      console.log("⏳ 等待AI优化完成后再进行粘贴...");
      
      // 注意:不在这里保存到数据库,由 useRecording.js 统一处理保存逻辑

      toast.success("🎤 语音识别完成,AI正在优化文本...");
    } else {
      console.log("❌ 转录失败或无文本:", transcriptionResult);
    }
  }, []);

  // 处理AI优化完成
  const handleAIOptimizationComplete = useCallback(async (optimizedResult) => {
    console.log('AI优化完成回调被触发:', optimizedResult);
    if (optimizedResult.success && optimizedResult.enhanced_by_ai && optimizedResult.text) {
      // 显示AI优化后的文本
      setProcessedText(optimizedResult.text);
      
      // 自动粘贴AI优化后的文本
      console.log("📋 准备粘贴AI优化后的文本:", optimizedResult.text);
      await safePaste(optimizedResult.text);
      console.log("✅ AI优化文本粘贴完成");
      
      toast.success("🤖 AI文本优化完成并已自动粘贴!");
      console.log('AI优化文本已设置:', optimizedResult.text);
    } else {
      console.warn('AI优化结果无效,使用原始文本:', optimizedResult);
      // 如果AI优化失败,则粘贴原始文本
      if (originalText) {
        console.log("📋 AI优化失败,粘贴原始文本:", originalText);
        await safePaste(originalText);
        toast.info("AI优化失败,已粘贴原始识别文本");
      }
    }
  }, [safePaste, originalText]);

  // 设置转录完成回调
  useEffect(() => {
    console.log('设置回调函数');
    window.onTranscriptionComplete = handleRecordingComplete;
    window.onAIOptimizationComplete = handleAIOptimizationComplete;
    
    // 验证回调函数是否正确设置
    console.log('回调函数设置完成:', {
      onTranscriptionComplete: typeof window.onTranscriptionComplete,
      onAIOptimizationComplete: typeof window.onAIOptimizationComplete
    });
    
    return () => {
      console.log('清理回调函数');
      window.onTranscriptionComplete = null;
      window.onAIOptimizationComplete = null;
    };
  }, [handleRecordingComplete, handleAIOptimizationComplete]);

  // 处理复制文本
  const handleCopyText = async (text) => {
    try {
      if (window.electronAPI) {
        const result = await window.electronAPI.copyText(text);
        if (result.success) {
          toast.success("文本已复制到剪贴板");
        } else {
          throw new Error(result.error || "复制失败");
        }
      } else {
        await navigator.clipboard.writeText(text);
        toast.success("文本已复制到剪贴板");
      }
    } catch (error) {
      console.error("复制文本失败:", error);
      toast.error(`无法复制文本到剪贴板: ${error.message}`);
    }
  };


  // 处理导出文本
  const handleExportText = async (text) => {
    try {
      if (window.electronAPI) {
        await window.electronAPI.exportTranscriptions('txt');
        toast.success("文本已导出到文件");
      } else {
        // Web环境下载文件
        const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `蛐蛐转录_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
        a.click();
        URL.revokeObjectURL(url);
      }
    } catch (error) {
      toast.error("无法导出文本文件");
    }
  };

  // 处理模型下载
  const handleDownloadModels = useCallback(async () => {
    try {
      // 显示开始下载的提示
      toast.info("📥 开始下载模型文件...");
      
      const result = await modelStatus.downloadModels();
      if (result.success) {
        toast.success("🎉 模型下载完成,正在加载...");
      } else {
        toast.error(`❌ 模型下载失败: ${result.error}`);
      }
    } catch (error) {
      console.error('下载模型失败:', error);
      toast.error(`❌ 模型下载失败: ${error.message}`);
    }
  }, [modelStatus]);

  // 切换录音状态
  const toggleRecording = useCallback(() => {
    // 检查模型状态
    if (modelStatus.stage === 'need_download') {
      toast.warning("📥 请先下载AI模型文件");
      return;
    }
    
    if (modelStatus.stage === 'downloading') {
      toast.warning("⬇️ 模型正在下载中,请稍候...");
      return;
    }
    
    if (modelStatus.stage === 'loading') {
      toast.warning("🤖 模型正在加载中,请稍候...");
      return;
    }
    
    if (modelStatus.stage === 'error') {
      toast.error(`❌ 模型错误: ${modelStatus.error}`);
      return;
    }
    
    if (!modelStatus.isReady) {
      toast.warning("⏳ 模型未就绪,请稍候...");
      return;
    }

    if (!isRecording && !isRecordingProcessing) {
      startRecording();
    } else if (isRecording) {
      stopRecording();
    }
  }, [modelStatus, isRecording, isRecordingProcessing, startRecording, stopRecording]);

  // 使用热键Hook,不再使用F2双击功能
  const { hotkey, syncRecordingState, registerHotkey } = useHotkey();

  // 注册传统热键监听 - 只在主窗口注册,避免重复
  useEffect(() => {
    // 检查是否为控制面板窗口
    const urlParams = new URLSearchParams(window.location.search);
    const isControlPanel = urlParams.get('panel') === 'control';
    
    // 只有主窗口才注册热键
    if (isControlPanel) {
      console.log('控制面板窗口,跳过热键注册');
      return;
    }

    const initializeHotkey = async () => {
      try {
        // 注册默认热键 CommandOrControl+Shift+Space
        const success = await registerHotkey('CommandOrControl+Shift+Space');
        if (success) {
          console.log('主窗口热键注册成功');
        } else {
          console.error('主窗口热键注册失败');
        }
      } catch (error) {
        console.error('主窗口热键注册异常:', error);
      }
    };

    if (registerHotkey) {
      initializeHotkey();
    }
  }, [registerHotkey]);

  // 处理关闭窗口
  const handleClose = () => {
    if (window.electronAPI) {
      window.electronAPI.hideWindow();
    }
  };

  // 处理打开设置
  const handleOpenSettings = () => {
    if (window.electronAPI) {
      window.electronAPI.openSettingsWindow();
    } else {
      // Web环境下仍然使用模态框
      setShowSettings(true);
    }
  };

  // 处理打开历史记录
  const handleOpenHistory = () => {
    if (window.electronAPI) {
      window.electronAPI.openHistoryWindow();
    }
  };


  // 监听全局热键触发事件
  useEffect(() => {
    if (window.electronAPI) {
      // 监听传统热键触发
      const unsubscribeHotkey = window.electronAPI.onHotkeyTriggered((event, data) => {
        console.log('收到热键触发事件:', data);
        console.log('当前录音状态:', isRecording, '处理状态:', isRecordingProcessing);
        toggleRecording();
      });

      // 监听旧的toggle事件(保持兼容性)
      const unsubscribeToggle = window.electronAPI.onToggleDictation(() => {
        console.log('收到旧版toggle事件');
        console.log('当前录音状态:', isRecording, '处理状态:', isRecordingProcessing);
        toggleRecording();
      });

      return () => {
        if (unsubscribeHotkey) unsubscribeHotkey();
        if (unsubscribeToggle) unsubscribeToggle();
      };
    }
  }, [toggleRecording, isRecording, isRecordingProcessing]);

  // 同步录音状态到热键管理器
  useEffect(() => {
    if (syncRecordingState) {
      syncRecordingState(isRecording);
    }
  }, [isRecording, syncRecordingState]);

  // 监听键盘事件
  useEffect(() => {
    const handleKeyPress = (e) => {
      if (e.key === "Escape") {
        handleClose();
      }
    };

    document.addEventListener("keydown", handleKeyPress);
    return () => document.removeEventListener("keydown", handleKeyPress);
  }, []);

  // 错误处理
  useEffect(() => {
    if (recordingError) {
      toast.error(recordingError);
    }
  }, [recordingError]);

  useEffect(() => {
    if (textProcessingError) {
      toast.error(textProcessingError);
    }
  }, [textProcessingError]);

  // 确定当前麦克风状态
  const getMicState = () => {
    if (isRecording) return "recording";
    if (isRecordingProcessing) return "processing";
    if (isOptimizing) return "optimizing";
    if (isHovered && !isRecording && !isRecordingProcessing && !isOptimizing) return "hover";
    return "idle";
  };

  const micState = getMicState();
  const isListening = isRecording || isRecordingProcessing;

  // 获取麦克风按钮属性
  const getMicButtonProps = () => {
    const baseClasses =
      "rounded-full w-16 h-16 flex items-center justify-center relative overflow-hidden border-2 border-white/80 transition-all duration-300 shadow-xl";

    // 统一的按钮样式,不再根据状态变色
    const buttonStyle = `${baseClasses} bg-gradient-to-br from-slate-100 to-slate-200 dark:from-gray-700 dark:to-gray-600 hover:from-slate-200 hover:to-slate-300 dark:hover:from-gray-600 dark:hover:to-gray-500 hover:shadow-2xl transform hover:scale-105`;

    // 如果模型未就绪,显示禁用状态(统一的灰色)
    if (!modelStatus.isReady) {
      return {
        className: `${baseClasses} bg-gradient-to-br from-gray-300 to-gray-400 dark:from-gray-600 dark:to-gray-700 cursor-not-allowed opacity-70`,
        tooltip: modelStatus.stage === 'need_download' ? "请先下载AI模型文件" :
                 modelStatus.stage === 'downloading' ? `模型下载中... ${modelStatus.downloadProgress || 0}%` :
                 modelStatus.stage === 'loading' ? "模型加载中,请稍候..." :
                 modelStatus.stage === 'error' ? `模型错误: ${modelStatus.error}` :
                 "模型未就绪,请稍候...",
        disabled: true
      };
    }

    switch (micState) {
      case "idle":
        return {
          className: `${buttonStyle} cursor-pointer`,
          tooltip: `按 [${hotkey}] 开始录音`,
          disabled: false
        };
      case "hover":
        return {
          className: `${buttonStyle} scale-105 shadow-2xl cursor-pointer`,
          tooltip: `按 [${hotkey}] 开始录音`,
          disabled: false
        };
      case "recording":
        return {
          className: `${buttonStyle} recording-pulse cursor-pointer`,
          tooltip: "正在录音...",
          disabled: false
        };
      case "processing":
        return {
          className: `${buttonStyle} cursor-not-allowed opacity-70`,
          tooltip: "正在识别语音...",
          disabled: true
        };
      case "optimizing":
        return {
          className: `${buttonStyle} cursor-not-allowed opacity-70`,
          tooltip: "AI正在优化文本...",
          disabled: true
        };
      default:
        return {
          className: `${buttonStyle} cursor-pointer`,
          tooltip: "点击开始录音",
          disabled: false
        };
    }
  };

  const micProps = getMicButtonProps();

  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 p-4 pb-4">
      {/* 主界面 */}
      <div className="max-w-2xl mx-auto min-h-screen flex flex-col">
        {/* 标题栏 */}
        <div
          className="flex items-center justify-between mb-8 draggable"
          onMouseDown={handleMouseDown}
          onMouseMove={handleMouseMove}
          onMouseUp={handleMouseUp}
        >
          <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100 chinese-title">
            蛐蛐
          </h1>
          <div className="flex items-center space-x-3 non-draggable">
            <Tooltip content="历史记录" position="bottom">
              <button
                onClick={handleOpenHistory}
                className="p-3 hover:bg-white/70 dark:hover:bg-gray-700/70 rounded-xl transition-colors shadow-sm"
              >
                <History className="w-6 h-6 text-gray-700 dark:text-gray-300" />
              </button>
            </Tooltip>
            <Tooltip content="设置" position="bottom">
              <button
                onClick={handleOpenSettings}
                className="p-3 hover:bg-white/70 dark:hover:bg-gray-700/70 rounded-xl transition-colors shadow-sm"
              >
                <Settings className="w-6 h-6 text-gray-700 dark:text-gray-300" />
              </button>
            </Tooltip>
          </div>
        </div>

        {/* 录音控制区域 */}
        <div className="text-center mb-8 flex-shrink-0">
          <Tooltip content={micProps.tooltip}>
            <button
              onClick={(e) => {
                if (handleClick(e) && !micProps.disabled) {
                  toggleRecording();
                }
              }}
              onMouseEnter={() => {
                if (!micProps.disabled) {
                  setIsHovered(true);
                }
              }}
              onMouseLeave={() => setIsHovered(false)}
              className={`${micProps.className} non-draggable shadow-lg`}
              disabled={micProps.disabled}
            >
              {/* 动态内容基于状态 */}
              {modelStatus.stage === 'downloading' ? (
                <LoadingIndicator size={20} />
              ) : modelStatus.stage === 'loading' || !modelStatus.isReady ? (
                <LoadingIndicator size={20} />
              ) : micState === "idle" ? (
                <SoundWaveIcon size={20} isActive={false} />
              ) : micState === "hover" ? (
                <SoundWaveIcon size={20} isActive={false} />
              ) : micState === "recording" ? (
                <SoundWaveIcon size={20} isActive={true} />
              ) : micState === "processing" ? (
                <VoiceWaveIndicator isListening={true} />
              ) : micState === "optimizing" ? (
                <LoadingIndicator size={20} />
              ) : null}

              {/* 移除所有状态指示环,保持简洁 */}
            </button>
          </Tooltip>
          
          <p className="mt-4 status-text text-gray-700 dark:text-gray-300">
            {modelStatus.stage === 'need_download' ? (
              "需要下载AI模型文件才能开始使用"
            ) : modelStatus.stage === 'downloading' ? (
              `正在下载模型文件... ${modelStatus.downloadProgress || 0}%`
            ) : modelStatus.stage === 'loading' ? (
              "模型加载中,请稍候..."
            ) : modelStatus.stage === 'error' ? (
              `模型错误: ${modelStatus.error}`
            ) : !modelStatus.isReady ? (
              "模型未就绪,请稍候..."
            ) : micState === "recording" ? (
              "正在录音,再次点击停止"
            ) : micState === "processing" ? (
              "正在识别语音..."
            ) : micState === "optimizing" ? (
              "AI正在优化文本,请稍候..."
            ) : (
              `点击麦克风或按 ${hotkey} 开始录音`
            )}
          </p>
        </div>

        {/* 模型下载进度显示 */}
        {(modelStatus.stage === 'need_download' || modelStatus.stage === 'downloading') && (
          <div className="mb-6">
            <ModelDownloadProgress
              modelStatus={modelStatus}
              onDownload={handleDownloadModels}
            />
          </div>
        )}

        {/* 文本显示区域 - 可滚动 */}
        <div className="flex-1 text-area-scroll">
          <TextDisplay
            originalText={originalText}
            processedText={processedText}
            isProcessing={isTextProcessing || isOptimizing}
            onCopy={handleCopyText}
            onExport={handleExportText}
            onPaste={safePaste}
          />
        </div>
      </div>

      {/* 设置面板 */}
      {showSettings && (
        <SettingsPanel onClose={() => setShowSettings(false)} />
      )}

    </div>
  );
}
```

## /src/components/SettingsPanel.jsx

```jsx path="/src/components/SettingsPanel.jsx" 
import React, { useState } from "react";
import { Mic, Shield, Settings } from "lucide-react";
import { usePermissions } from "../hooks/usePermissions";
import PermissionCard from "./ui/permission-card";
import { toast } from "sonner";

const SettingsPanel = ({ onClose }) => {
  const showAlert = (alert) => {
    toast(alert.title, {
      description: alert.description,
      duration: 4000,
    });
  };

  const {
    micPermissionGranted,
    accessibilityPermissionGranted,
    requestMicPermission,
    testAccessibilityPermission,
  } = usePermissions(showAlert);


  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
        {/* 标题栏 */}
        <div className="flex items-center justify-between p-6 border-b">
          <div className="flex items-center gap-3">
            <Settings className="w-6 h-6 text-blue-600" />
            <h2 className="text-xl font-bold text-gray-900 chinese-title">设置</h2>
          </div>
          <button
            onClick={onClose}
            className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
          >
            <span className="text-gray-500 text-xl">×</span>
          </button>
        </div>

        {/* 内容区域 */}
        <div className="p-6 space-y-8">
          {/* 权限部分 */}
          <div>
            <h3 className="text-lg font-semibold text-gray-900 mb-4 chinese-title">
              权限管理
            </h3>
            <p className="text-sm text-gray-600 mb-6">
              测试和管理应用权限,确保麦克风和辅助功能正常工作。
            </p>
            
            <div className="space-y-4">
              <PermissionCard
                icon={Mic}
                title="麦克风权限"
                description="录制语音所需的权限"
                granted={micPermissionGranted}
                onRequest={requestMicPermission}
                buttonText="测试麦克风"
              />

              <PermissionCard
                icon={Shield}
                title="辅助功能权限"
                description="自动粘贴文本所需的权限"
                granted={accessibilityPermissionGranted}
                onRequest={testAccessibilityPermission}
                buttonText="测试权限"
              />
            </div>
          </div>

          {/* 应用信息部分 */}
          <div className="border-t pt-8">
            <h3 className="text-lg font-semibold text-gray-900 mb-4 chinese-title">
              关于蛐蛐
            </h3>
            <div className="bg-gradient-to-r from-blue-50 to-green-50 p-4 rounded-lg">
              <p className="text-sm text-gray-700 mb-2">
                🎤 <strong>蛐蛐 (QuQu)</strong> - 基于FunASR和AI的中文语音转文字应用
              </p>
              <p className="text-xs text-gray-600">
                • 高精度中文语音识别<br/>
                • AI智能文本优化<br/>
                • 实时语音处理<br/>
                • 隐私保护设计
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default SettingsPanel;
```

## /src/components/ui/button.jsx

```jsx path="/src/components/ui/button.jsx" 
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";

import { cn } from "../../lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
  const Comp = asChild ? Slot : "button"
  return (
    <Comp
      className={cn(buttonVariants({ variant, size, className }))}
      ref={ref}
      {...props} />
  );
})
Button.displayName = "Button"

export { Button, buttonVariants }

```

## /src/components/ui/card.jsx

```jsx path="/src/components/ui/card.jsx" 
import * as React from "react"

import { cn } from "../../lib/utils"

const Card = React.forwardRef(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
    {...props} />
))
Card.displayName = "Card"

const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex flex-col space-y-1.5 p-6", className)}
    {...props} />
))
CardHeader.displayName = "CardHeader"

const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
    {...props} />
))
CardTitle.displayName = "CardTitle"

const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-sm text-muted-foreground", className)}
    {...props} />
))
CardDescription.displayName = "CardDescription"

const CardContent = React.forwardRef(({ className, ...props }, ref) => (
  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"

const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex items-center p-6 pt-0", className)}
    {...props} />
))
CardFooter.displayName = "CardFooter"

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

```

## /src/components/ui/history-modal.jsx

```jsx path="/src/components/ui/history-modal.jsx" 
import React, { useState, useEffect } from "react";
import { X, Copy, Trash2, Search, Calendar, FileText } from "lucide-react";
import { toast } from "sonner";

const HistoryModal = ({ isOpen, onClose, onCopy }) => {
  const [transcriptions, setTranscriptions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [searchQuery, setSearchQuery] = useState("");
  const [filteredTranscriptions, setFilteredTranscriptions] = useState([]);

  // 加载转录历史
  const loadTranscriptions = async () => {
    if (!window.electronAPI) return;
    
    setLoading(true);
    try {
      const result = await window.electronAPI.getTranscriptions(100, 0);
      setTranscriptions(result || []);
      setFilteredTranscriptions(result || []);
    } catch (error) {
      console.error("加载历史记录失败:", error);
      toast.error("加载历史记录失败");
    } finally {
      setLoading(false);
    }
  };

  // 搜索功能
  useEffect(() => {
    if (!searchQuery.trim()) {
      setFilteredTranscriptions(transcriptions);
    } else {
      const filtered = transcriptions.filter(item => 
        item.text?.toLowerCase().includes(searchQuery.toLowerCase()) ||
        item.processed_text?.toLowerCase().includes(searchQuery.toLowerCase())
      );
      setFilteredTranscriptions(filtered);
    }
  }, [searchQuery, transcriptions]);

  // 当模态框打开时加载数据
  useEffect(() => {
    if (isOpen) {
      loadTranscriptions();
    }
  }, [isOpen]);

  // 删除转录记录
  const handleDelete = async (id) => {
    if (!window.electronAPI) return;
    
    try {
      await window.electronAPI.deleteTranscription(id);
      setTranscriptions(prev => prev.filter(item => item.id !== id));
      toast.success("记录已删除");
    } catch (error) {
      console.error("删除记录失败:", error);
      toast.error("删除记录失败");
    }
  };

  // 复制文本
  const handleCopy = async (text) => {
    try {
      if (onCopy) {
        await onCopy(text);
      } else if (window.electronAPI) {
        await window.electronAPI.copyText(text);
        toast.success("文本已复制到剪贴板");
      }
    } catch (error) {
      toast.error("复制失败");
    }
  };

  // 格式化日期
  const formatDate = (dateString) => {
    const date = new Date(dateString);
    const now = new Date();
    const diffTime = Math.abs(now - date);
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    if (diffDays === 1) {
      return `今天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
    } else if (diffDays === 2) {
      return `昨天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
    } else if (diffDays <= 7) {
      return `${diffDays - 1}天前`;
    } else {
      return date.toLocaleDateString('zh-CN', { 
        month: 'short', 
        day: 'numeric',
        hour: '2-digit',
        minute: '2-digit'
      });
    }
  };

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
      <div className="bg-white rounded-xl shadow-2xl w-full max-w-4xl max-h-[80vh] flex flex-col">
        {/* 头部 */}
        <div className="flex items-center justify-between p-6 border-b border-gray-200">
          <div className="flex items-center space-x-3">
            <FileText className="w-6 h-6 text-gray-700" />
            <h2 className="text-xl font-semibold text-gray-900 chinese-title">转录历史</h2>
            <span className="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-sm">
              {filteredTranscriptions.length} 条记录
            </span>
          </div>
          <button
            onClick={onClose}
            className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
          >
            <X className="w-5 h-5 text-gray-500" />
          </button>
        </div>

        {/* 搜索栏 */}
        <div className="p-6 border-b border-gray-100">
          <div className="relative">
            <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
            <input
              type="text"
              placeholder="搜索转录内容..."
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent chinese-text"
            />
          </div>
        </div>

        {/* 内容区域 */}
        <div className="flex-1 overflow-y-auto p-6">
          {loading ? (
            <div className="flex items-center justify-center py-12">
              <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
              <span className="ml-3 text-gray-600">加载中...</span>
            </div>
          ) : filteredTranscriptions.length === 0 ? (
            <div className="text-center py-12">
              <FileText className="w-12 h-12 text-gray-300 mx-auto mb-4" />
              <p className="text-gray-500 chinese-text">
                {searchQuery ? "没有找到匹配的记录" : "暂无转录历史"}
              </p>
            </div>
          ) : (
            <div className="space-y-4">
              {filteredTranscriptions.map((item) => (
                <div
                  key={item.id}
                  className="bg-gray-50 rounded-lg p-4 hover:bg-gray-100 transition-colors"
                >
                  <div className="flex items-start justify-between mb-3">
                    <div className="flex items-center space-x-2 text-sm text-gray-500">
                      <Calendar className="w-4 h-4" />
                      <span>{formatDate(item.created_at)}</span>
                      {item.confidence && (
                        <span className="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs">
                          置信度: {Math.round(item.confidence * 100)}%
                        </span>
                      )}
                    </div>
                    <div className="flex space-x-2">
                      <button
                        onClick={() => handleCopy(item.processed_text || item.text)}
                        className="p-1 hover:bg-gray-200 rounded transition-colors"
                        title="复制文本"
                      >
                        <Copy className="w-4 h-4 text-gray-600" />
                      </button>
                      <button
                        onClick={() => handleDelete(item.id)}
                        className="p-1 hover:bg-red-100 rounded transition-colors"
                        title="删除记录"
                      >
                        <Trash2 className="w-4 h-4 text-red-500" />
                      </button>
                    </div>
                  </div>

                  {/* 原始文本 */}
                  {item.text && (
                    <div className="mb-3">
                      <h4 className="text-sm font-medium text-gray-700 mb-1">原始识别:</h4>
                      <p className="text-gray-900 chinese-content leading-relaxed bg-white p-3 rounded border">
                        {item.text}
                      </p>
                    </div>
                  )}

                  {/* AI优化文本 */}
                  {item.processed_text && item.processed_text !== item.text && (
                    <div>
                      <h4 className="text-sm font-medium text-emerald-700 mb-1">AI优化:</h4>
                      <p className="text-gray-900 chinese-content leading-relaxed bg-emerald-50 p-3 rounded border border-emerald-200">
                        {item.processed_text}
                      </p>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>

        {/* 底部操作栏 */}
        {filteredTranscriptions.length > 0 && (
          <div className="p-6 border-t border-gray-200 bg-gray-50 rounded-b-xl">
            <div className="flex items-center justify-between">
              <p className="text-sm text-gray-600">
                共 {filteredTranscriptions.length} 条记录
              </p>
              <button
                onClick={() => {
                  if (window.electronAPI) {
                    window.electronAPI.exportTranscriptions('txt');
                    toast.success("导出功能已触发");
                  }
                }}
                className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm"
              >
                导出全部
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

export default HistoryModal;
```

## /src/components/ui/input.jsx

```jsx path="/src/components/ui/input.jsx" 
import * as React from "react"

import { cn } from "src/lib/utils"

const Input = React.forwardRef(({ className, type, ...props }, ref) => {
  return (
    <input
      type={type}
      className={cn(
        "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        className
      )}
      ref={ref}
      {...props} />
  );
})
Input.displayName = "Input"

export { Input }

```

## /src/components/ui/label.jsx

```jsx path="/src/components/ui/label.jsx" 
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva } from "class-variance-authority";

import { cn } from "src/lib/utils"

const labelVariants = cva(
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)

const Label = React.forwardRef(({ className, ...props }, ref) => (
  <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName

export { Label }

```

## /src/components/ui/loading-dots.jsx

```jsx path="/src/components/ui/loading-dots.jsx" 
import React, { useState, useEffect } from 'react';

export const LoadingDots = () => {
  const [tick, setTick] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => setTick(t => t + 1), 350);
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div className="flex items-end h-3 gap-1">
      {[0, 1, 2].map(i => (
        <div
          key={i}
          className={`w-1 bg-white rounded-full transition-all duration-200 ${
            tick % 3 === i ? 'h-3' : 'h-1.5'
          }`}
          style={{
            opacity: 0.9,
          }}
        />
      ))}
    </div>
  );
};
```

## /src/components/ui/model-status-indicator.jsx

```jsx path="/src/components/ui/model-status-indicator.jsx" 
import React from 'react';
import { CheckCircle, AlertCircle, Loader2, Download, Clock } from 'lucide-react';

/**
 * 模型状态指示器组件
 * 显示FunASR模型的下载、加载状态
 */
export const ModelStatusIndicator = ({ modelStatus, className = "", onDownload = null }) => {
  const getStatusIcon = () => {
    switch (modelStatus.stage) {
      case 'checking':
        return <Loader2 className="w-4 h-4 animate-spin text-blue-500 model-loading" />;
      case 'need_download':
        return <Download className="w-4 h-4 text-orange-500" />;
      case 'downloading':
        return <Loader2 className="w-4 h-4 animate-spin text-blue-500 model-downloading" />;
      case 'loading':
        return <Clock className="w-4 h-4 text-blue-500 model-loading" />;
      case 'ready':
        return <CheckCircle className="w-4 h-4 text-green-500 model-ready" />;
      case 'error':
        return <AlertCircle className="w-4 h-4 text-red-500 model-error" />;
      default:
        return <Download className="w-4 h-4 text-gray-500" />;
    }
  };

  const getStatusText = () => {
    switch (modelStatus.stage) {
      case 'checking':
        return "检查模型状态...";
      case 'need_download':
        return "需要下载模型";
      case 'downloading':
        return "正在下载模型...";
      case 'loading':
        return "模型加载中...";
      case 'ready':
        return "模型已就绪";
      case 'error':
        return "模型错误";
      default:
        return "模型状态未知";
    }
  };

  const getStatusColor = () => {
    switch (modelStatus.stage) {
      case 'checking':
      case 'downloading':
      case 'loading':
        return "text-blue-600";
      case 'need_download':
        return "text-orange-600";
      case 'ready':
        return "text-green-600";
      case 'error':
        return "text-red-600";
      default:
        return "text-gray-600";
    }
  };

  const getProgressText = () => {
    if (modelStatus.isDownloading && modelStatus.downloadProgress > 0) {
      return `${modelStatus.downloadProgress}%`;
    }
    if (modelStatus.isLoading && modelStatus.progress > 0) {
      return `${modelStatus.progress}%`;
    }
    return null;
  };

  return (
    <div className={`flex items-center space-x-2 ${className}`}>
      {getStatusIcon()}
      <span className={`text-sm font-medium ${getStatusColor()}`}>
        {getStatusText()}
      </span>
      {getProgressText() && (
        <span className="text-xs text-gray-500">
          ({getProgressText()})
        </span>
      )}
      {modelStatus.stage === 'need_download' && onDownload && (
        <button
          onClick={onDownload}
          className="ml-2 px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
        >
          下载
        </button>
      )}
    </div>
  );
};

/**
 * 简化的模型状态图标组件
 * 仅显示图标,用于空间受限的地方
 */
export const ModelStatusIcon = ({ modelStatus, size = "w-5 h-5", showTooltip = true }) => {
  const getStatusIcon = () => {
    switch (modelStatus.stage) {
      case 'checking':
        return <Loader2 className={`${size} animate-spin text-blue-500 model-loading`} />;
      case 'need_download':
        return <Download className={`${size} text-orange-500`} />;
      case 'downloading':
        return <Loader2 className={`${size} animate-spin text-blue-500 model-downloading`} />;
      case 'loading':
        return <Clock className={`${size} text-blue-500 model-loading`} />;
      case 'ready':
        return <CheckCircle className={`${size} text-green-500 model-ready`} />;
      case 'error':
        return <AlertCircle className={`${size} text-red-500 model-error`} />;
      default:
        return <Download className={`${size} text-gray-500`} />;
    }
  };

  const getTooltipText = () => {
    switch (modelStatus.stage) {
      case 'checking':
        return "🔍 正在检查模型状态...";
      case 'need_download':
        return "📥 需要下载AI模型文件(约1.1GB)";
      case 'downloading':
        return `⬇️ 正在下载模型文件... ${modelStatus.downloadProgress || 0}%`;
      case 'loading':
        return "🤖 AI模型加载中,请稍候...";
      case 'ready':
        return "✅ AI模型已就绪,可以开始语音识别";
      case 'error':
        return `❌ 模型错误: ${modelStatus.error || '未知错误'}`;
      default:
        return "⏳ 模型状态未知";
    }
  };

  const icon = getStatusIcon();

  if (!showTooltip) {
    return icon;
  }

  return (
    <div className="relative group">
      {icon}
      <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-white model-status-tooltip rounded-lg whitespace-nowrap z-10 opacity-0 group-hover:opacity-100 transition-all duration-200 pointer-events-none">
        <span className="text-xs font-medium">{getTooltipText()}</span>
        <div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-t-2 border-transparent border-t-black/85"></div>
      </div>
    </div>
  );
};

/**
 * 模型下载进度组件
 * 显示详细的下载进度信息
 */
export const ModelDownloadProgress = ({ modelStatus, onDownload, onCancel }) => {
  if (modelStatus.stage === 'need_download') {
    return (
      <div className="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-700 rounded-lg p-4">
        <div className="flex items-center justify-between">
          <div className="flex items-center space-x-3">
            <Download className="w-5 h-5 text-orange-500" />
            <div>
              <h3 className="text-sm font-medium text-orange-800 dark:text-orange-200">
                需要下载AI模型
              </h3>
              <p className="text-xs text-orange-600 dark:text-orange-300">
                首次使用需要下载约1.1GB的模型文件
              </p>
            </div>
          </div>
          <button
            onClick={onDownload}
            disabled={modelStatus.isDownloading}
            className={`px-4 py-2 text-white rounded-lg transition-colors text-sm font-medium ${
              modelStatus.isDownloading
                ? 'bg-gray-400 cursor-not-allowed'
                : 'bg-orange-500 hover:bg-orange-600'
            }`}
          >
            {modelStatus.isDownloading ? '准备下载...' : '开始下载'}
          </button>
        </div>
      </div>
    );
  }

  if (modelStatus.stage === 'downloading') {
    return (
      <div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-700 rounded-lg p-4">
        <div className="space-y-3">
          <div className="flex items-center justify-between">
            <div className="flex items-center space-x-3">
              <Loader2 className="w-5 h-5 animate-spin text-blue-500" />
              <div>
                <h3 className="text-sm font-medium text-blue-800 dark:text-blue-200">
                  正在下载模型文件
                </h3>
                <p className="text-xs text-blue-600 dark:text-blue-300">
                  请保持网络连接,下载可能需要几分钟
                </p>
              </div>
            </div>
            {onCancel && (
              <button
                onClick={onCancel}
                className="px-3 py-1 text-xs text-blue-600 hover:text-blue-800 transition-colors"
              >
                取消
              </button>
            )}
          </div>
          
          {/* 进度条 */}
          <div className="space-y-2">
            <div className="flex justify-between text-xs text-blue-600 dark:text-blue-300">
              <span>下载进度</span>
              <span>{modelStatus.downloadProgress || 0}%</span>
            </div>
            <div className="w-full bg-blue-200 dark:bg-blue-800 rounded-full h-2">
              <div
                className="bg-blue-500 h-2 rounded-full transition-all duration-300"
                style={{ width: `${modelStatus.downloadProgress || 0}%` }}
              />
            </div>
          </div>
        </div>
      </div>
    );
  }

  return null;
};
```

## /src/components/ui/permission-card.jsx

```jsx path="/src/components/ui/permission-card.jsx" 
import React from "react";
import { Check } from "lucide-react";

const PermissionCard = ({
  icon: Icon,
  title,
  description,
  granted,
  onRequest,
  buttonText = "授予权限",
}) => {
  return (
    <div className="border border-gray-200 dark:border-gray-600 rounded-md p-2 bg-white dark:bg-gray-700 shadow-sm">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1.5">
          <Icon className="w-3 h-3 text-blue-600 dark:text-blue-400 flex-shrink-0" />
          <div className="min-w-0">
            <h3 className="text-xs font-medium text-gray-900 dark:text-gray-100 chinese-title truncate">{title}</h3>
            <p className="text-xs text-gray-600 dark:text-gray-400 truncate">{description}</p>
          </div>
        </div>
        {granted ? (
          <div className="text-green-600 dark:text-green-400 flex items-center gap-1 flex-shrink-0">
            <Check className="w-3 h-3" />
            <span className="text-xs font-medium">已授予</span>
          </div>
        ) : (
          <button
            onClick={onRequest}
            className="px-2 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded transition-colors flex-shrink-0"
          >
            {buttonText}
          </button>
        )}
      </div>
    </div>
  );
};

export default PermissionCard;
```

## /src/components/ui/sonner.jsx

```jsx path="/src/components/ui/sonner.jsx" 
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"

const Toaster = ({
  ...props
}) => {
  const { theme = "system" } = useTheme()

  return (
    <Sonner
      theme={theme}
      className="toaster group"
      toastOptions={{
        classNames: {
          toast:
            "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
          description: "group-[.toast]:text-muted-foreground",
          actionButton:
            "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
          cancelButton:
            "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
        },
      }}
      {...props} />
  );
}

export { Toaster }

```

## /src/components/ui/status-light.jsx

```jsx path="/src/components/ui/status-light.jsx" 
import React from 'react';

/**
 * 状态灯组件
 * 使用三种颜色的圆形指示灯显示模型状态
 */
export const StatusLight = ({ modelStatus, size = "w-3 h-3", showTooltip = true }) => {
  const getLightColor = () => {
    if (modelStatus.isLoading) {
      return "bg-yellow-500"; // 黄灯 - 加载中
    }
    
    if (modelStatus.error) {
      return "bg-red-500"; // 红灯 - 错误
    }
    
    if (modelStatus.isReady) {
      return "bg-green-500"; // 绿灯 - 就绪
    }
    
    return "bg-gray-400"; // 灰灯 - 未知状态
  };

  const getLightAnimation = () => {
    if (modelStatus.isLoading) {
      return "animate-pulse"; // 黄灯闪烁
    }
    
    if (modelStatus.error) {
      return "model-error"; // 红灯闪烁
    }
    
    if (modelStatus.isReady) {
      return "model-ready"; // 绿灯脉冲
    }
    
    return "";
  };

  const getTooltipText = () => {
    if (modelStatus.isLoading) {
      return "🟡 模型加载中";
    }
    
    if (modelStatus.error) {
      return "🔴 模型加载失败";
    }
    
    if (modelStatus.isReady) {
      return "🟢 模型已就绪";
    }
    
    return "⚪ 模型状态未知";
  };

  const lightElement = (
    <div 
      className={`${size} rounded-full ${getLightColor()} ${getLightAnimation()} border border-white/30 shadow-sm`}
    />
  );

  if (!showTooltip) {
    return lightElement;
  }

  return (
    <div className="relative group">
      {lightElement}
      <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 text-white model-status-tooltip rounded-md whitespace-nowrap z-10 opacity-0 group-hover:opacity-100 transition-all duration-200 pointer-events-none">
        <span className="text-xs font-medium">{getTooltipText()}</span>
        <div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-t-2 border-transparent border-t-black/85"></div>
      </div>
    </div>
  );
};
```

## /src/components/ui/tabs.jsx

```jsx path="/src/components/ui/tabs.jsx" 
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "../../lib/utils"

const Tabs = TabsPrimitive.Root

const TabsList = React.forwardRef(({ className, ...props }, ref) => (
  <TabsPrimitive.List
    ref={ref}
    className={cn(
      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
      className
    )}
    {...props}
  />
))
TabsList.displayName = TabsPrimitive.List.displayName

const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
  <TabsPrimitive.Trigger
    ref={ref}
    className={cn(
      "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
      className
    )}
    {...props}
  />
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName

const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
  <TabsPrimitive.Content
    ref={ref}
    className={cn(
      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
      className
    )}
    {...props}
  />
))
TabsContent.displayName = TabsPrimitive.Content.displayName

export { Tabs, TabsList, TabsTrigger, TabsContent }
```

## /src/helpers/clipboard.js

```js path="/src/helpers/clipboard.js" 
const { clipboard } = require("electron");
const { spawn } = require("child_process");

class ClipboardManager {
  constructor(logger) {
    // 初始化剪贴板管理器
    this.logger = logger;
    
    // 尝试加载 osascript 模块(仅在 macOS 上)
    this.osascript = null;
    if (process.platform === "darwin") {
      try {
        this.osascript = require("osascript");
        this.safeLog("✅ osascript 模块加载成功");
      } catch (error) {
        this.safeLog("⚠️ osascript 模块加载失败,将使用备用方法", error.message);
      }
    }
  }

  // 安全日志方法 - 使用logManager记录
  safeLog(message, data = null) {
    if (this.logger) {
      try {
        this.logger.info(message, data);
      } catch (error) {
        // 静默忽略 EPIPE 错误
        if (error.code !== "EPIPE") {
          process.stderr.write(`日志错误: ${error.message}\n`);
        }
      }
    }
  }

  // 简化的 macOS accessibility 检查
  async enableMacOSAccessibility() {
    if (process.platform !== "darwin") return true;
    
    try {
      this.safeLog("🔧 检查 macOS accessibility 权限");
      
      // 简化为基本的权限检查,不设置复杂的AXManualAccessibility
      const script = `
        tell application "System Events"
          set frontApp to name of first application process whose frontmost is true
          return frontApp
        end tell
      `;
      
      const testProcess = spawn("osascript", ["-e", script]);
      
      return new Promise((resolve) => {
        testProcess.on("close", (code) => {
          if (code === 0) {
            this.safeLog("✅ macOS accessibility 权限正常");
            resolve(true);
          } else {
            this.safeLog("⚠️ macOS accessibility 权限不足");
            resolve(false);
          }
        });
        
        testProcess.on("error", () => {
          this.safeLog("❌ accessibility 权限检查失败");
          resolve(false);
        });
      });
    } catch (error) {
      this.safeLog("❌ 检查 macOS accessibility 时出错:", error.message);
      return false;
    }
  }

  // 简化的文本插入方法 - 直接使用标准粘贴方式
  async insertTextDirectly(text) {
    // 简化实现,直接使用标准的粘贴方法
    this.safeLog("🎯 使用标准粘贴方式插入文本");
    return await this.pasteText(text);
  }

  async pasteText(text) {
    try {
      // 首先保存原始剪贴板内容
      const originalClipboard = clipboard.readText();
      this.safeLog(
        "💾 已保存原始剪贴板内容",
        originalClipboard.substring(0, 50) + "..."
      );

      // 将文本复制到剪贴板 - 这总是有效的
      clipboard.writeText(text);
      this.safeLog(
        "📋 文本已复制到剪贴板",
        text.substring(0, 50) + "..."
      );

      if (process.platform === "darwin") {
        // 简化权限检查,直接尝试粘贴
        this.safeLog("🔍 检查粘贴操作的辅助功能权限");
        const hasPermissions = await this.checkAccessibilityPermissions();

        if (!hasPermissions) {
          this.safeLog("⚠️ 没有辅助功能权限 - 文本仅复制到剪贴板");
          const errorMsg =
            "需要辅助功能权限才能自动粘贴。文本已复制到剪贴板 - 请手动使用 Cmd+V 粘贴。";
          throw new Error(errorMsg);
        }

        this.safeLog("✅ 权限已授予,尝试粘贴");
        return await this.pasteMacOS(originalClipboard);
      } else if (process.platform === "win32") {
        return await this.pasteWindows(originalClipboard);
      } else {
        return await this.pasteLinux(originalClipboard);
      }
    } catch (error) {
      throw error;
    }
  }

  async pasteMacOS(originalClipboard) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const pasteProcess = spawn("osascript", [
          "-e",
          'tell application "System Events" to keystroke "v" using command down',
        ]);

        let errorOutput = "";
        let hasTimedOut = false;

        pasteProcess.stderr.on("data", (data) => {
          errorOutput += data.toString();
        });

        pasteProcess.on("close", (code) => {
          if (hasTimedOut) return;

          // 首先清除超时
          clearTimeout(timeoutId);

          // 清理进程引用
          pasteProcess.removeAllListeners();

          if (code === 0) {
            this.safeLog("✅ 通过 Cmd+V 模拟成功粘贴文本");
            setTimeout(() => {
              clipboard.writeText(originalClipboard);
              this.safeLog("🔄 原始剪贴板内容已恢复");
            }, 100);
            resolve();
          } else {
            const errorMsg = `粘贴失败 (代码 ${code})。文本已复制到剪贴板 - 请手动使用 Cmd+V 粘贴。`;
            reject(new Error(errorMsg));
          }
        });

        pasteProcess.on("error", (error) => {
          if (hasTimedOut) return;
          clearTimeout(timeoutId);
          pasteProcess.removeAllListeners();
          const errorMsg = `粘贴命令失败: ${error.message}。文本已复制到剪贴板 - 请手动使用 Cmd+V 粘贴。`;
          reject(new Error(errorMsg));
        });

        const timeoutId = setTimeout(() => {
          hasTimedOut = true;
          pasteProcess.kill("SIGKILL");
          pasteProcess.removeAllListeners();
          const errorMsg =
            "粘贴操作超时。文本已复制到剪贴板 - 请手动使用 Cmd+V 粘贴。";
          reject(new Error(errorMsg));
        }, 3000);
      }, 100);
    });
  }

  async pasteWindows(originalClipboard) {
    return new Promise((resolve, reject) => {
      const pasteProcess = spawn("powershell", [
        "-Command",
        'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait("^v")',
      ]);

      pasteProcess.on("close", (code) => {
        if (code === 0) {
          // 文本粘贴成功
          setTimeout(() => {
            clipboard.writeText(originalClipboard);
          }, 100);
          resolve();
        } else {
          reject(
            new Error(
              `Windows 粘贴失败,代码 ${code}。文本已复制到剪贴板。`
            )
          );
        }
      });

      pasteProcess.on("error", (error) => {
        reject(
          new Error(
            `Windows 粘贴失败: ${error.message}。文本已复制到剪贴板。`
          )
        );
      });
    });
  }

  async pasteLinux(originalClipboard) {
    return new Promise((resolve, reject) => {
      const pasteProcess = spawn("xdotool", ["key", "ctrl+v"]);

      pasteProcess.on("close", (code) => {
        if (code === 0) {
          // 文本粘贴成功
          setTimeout(() => {
            clipboard.writeText(originalClipboard);
          }, 100);
          resolve();
        } else {
          reject(
            new Error(
              `Linux 粘贴失败,代码 ${code}。文本已复制到剪贴板。`
            )
          );
        }
      });

      pasteProcess.on("error", (error) => {
        reject(
          new Error(
            `Linux 粘贴失败: ${error.message}。文本已复制到剪贴板。`
          )
        );
      });
    });
  }

  async checkAccessibilityPermissions() {
    if (process.platform !== "darwin") return true;

    return new Promise((resolve) => {
      // 检查辅助功能权限
      const testProcess = spawn("osascript", [
        "-e",
        'tell application "System Events" to get name of first process',
      ]);

      let testOutput = "";
      let testError = "";

      testProcess.stdout.on("data", (data) => {
        testOutput += data.toString();
      });

      testProcess.stderr.on("data", (data) => {
        testError += data.toString();
      });

      testProcess.on("close", (code) => {
        if (code === 0) {
          resolve(true);
        } else {
          this.showAccessibilityDialog(testError);
          resolve(false);
        }
      });

      testProcess.on("error", (error) => {
        resolve(false);
      });
    });
  }

  showAccessibilityDialog(testError) {
    const isStuckPermission =
      testError.includes("not allowed assistive access") ||
      testError.includes("(-1719)") ||
      testError.includes("(-25006)");

    let dialogMessage;
    if (isStuckPermission) {
      dialogMessage = `🔒 蛐蛐需要辅助功能权限,但看起来您可能有来自先前版本的旧权限。

❗ 常见问题:如果您重新构建/重新安装了蛐蛐,旧权限可能"卡住"并阻止新权限。

🔧 解决方法:
1. 打开系统设置 → 隐私与安全性 → 辅助功能
2. 查找任何旧的"蛐蛐"条目并删除它们(点击 - 按钮)
3. 同时删除任何显示"Electron"或名称不明确的条目
4. 点击 + 按钮并手动添加新的蛐蛐应用
5. 确保复选框已启用
6. 重启蛐蛐

⚠️ 这在开发期间重新构建应用时特别常见。

📝 没有此权限,文本将只复制到剪贴板(无自动粘贴)。

您想现在打开系统设置吗?`;
    } else {
      dialogMessage = `🔒 蛐蛐需要辅助功能权限才能将文本粘贴到其他应用程序中。

📋 当前状态:剪贴板复制有效,但粘贴(Cmd+V 模拟)失败。

🔧 解决方法:
1. 打开系统设置(或较旧 macOS 上的系统偏好设置)
2. 转到隐私与安全性 → 辅助功能
3. 点击锁图标并输入您的密码
4. 将蛐蛐添加到列表中并勾选复选框
5. 重启蛐蛐

⚠️ 没有此权限,听写文本将只复制到剪贴板但不会自动粘贴。

💡 在生产版本中,此权限是完整功能所必需的。

您想现在打开系统设置吗?`;
    }

    const permissionDialog = spawn("osascript", [
      "-e",
      `display dialog "${dialogMessage}" buttons {"取消", "打开系统设置"} default button "打开系统设置"`,
    ]);

    permissionDialog.on("close", (dialogCode) => {
      if (dialogCode === 0) {
        this.openSystemSettings();
      }
    });

    permissionDialog.on("error", (error) => {
      // 权限对话框错误 - 用户需要手动授予权限
    });
  }

  openSystemSettings() {
    const settingsCommands = [
      [
        "open",
        [
          "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility",
        ],
      ],
      ["open", ["-b", "com.apple.systempreferences"]],
      ["open", ["/System/Library/PreferencePanes/Security.prefPane"]],
    ];

    let commandIndex = 0;
    const tryNextCommand = () => {
      if (commandIndex < settingsCommands.length) {
        const [cmd, args] = settingsCommands[commandIndex];
        const settingsProcess = spawn(cmd, args);

        settingsProcess.on("error", (error) => {
          commandIndex++;
          tryNextCommand();
        });

        settingsProcess.on("close", (settingsCode) => {
          if (settingsCode !== 0) {
            commandIndex++;
            tryNextCommand();
          }
        });
      } else {
        // 所有设置命令都失败,尝试后备方案
        spawn("open", ["-a", "System Preferences"]).on("error", () => {
          spawn("open", ["-a", "System Settings"]).on("error", () => {
            // 无法打开设置应用
          });
        });
      }
    };

    tryNextCommand();
  }

  /**
   * 复制文本到剪贴板
   * @param {string} text - 要复制的文本
   * @returns {Promise<{success: boolean}>}
   */
  async copyText(text) {
    try {
      clipboard.writeText(text);
      return { success: true };
    } catch (error) {
      throw error;
    }
  }

  /**
   * 从剪贴板读取文本
   * @returns {Promise<string>}
   */
  async readClipboard() {
    try {
      const text = clipboard.readText();
      return text;
    } catch (error) {
      throw error;
    }
  }

  /**
   * 将文本写入剪贴板
   * @param {string} text - 要写入的文本
   * @returns {Promise<{success: boolean}>}
   */
  async writeClipboard(text) {
    try {
      clipboard.writeText(text);
      return { success: true };
    } catch (error) {
      throw error;
    }
  }
}

module.exports = ClipboardManager;
```

## /src/helpers/database.js

```js path="/src/helpers/database.js" 
const Database = require("better-sqlite3");
const path = require("path");
const fs = require("fs");

class DatabaseManager {
  constructor(logger = null) {
    this.db = null;
    this.dbPath = null;
    this.logger = logger;
  }

  initialize(dataDirectory) {
    this.dbPath = path.join(dataDirectory, "transcriptions.db");
    
    // 确保数据目录存在
    const dir = path.dirname(this.dbPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }

    this.db = new Database(this.dbPath);
    this.createTables();
  }

  createTables() {
    // 创建转录记录表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS transcriptions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        text TEXT NOT NULL,
        raw_text TEXT,
        processed_text TEXT,
        confidence REAL,
        language TEXT DEFAULT 'zh-CN',
        duration REAL,
        file_size INTEGER,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // 创建设置表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS settings (
        key TEXT PRIMARY KEY,
        value TEXT,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // 创建索引
    this.db.exec(`
      CREATE INDEX IF NOT EXISTS idx_transcriptions_created_at 
      ON transcriptions(created_at DESC)
    `);
  }

  saveTranscription(data) {
    // 验证必需的数据
    if (!data || typeof data !== 'object') {
      throw new Error('转录数据无效');
    }

    // 确保text字段存在且不为空
    const text = data.text || data.raw_text || '';
    if (!text || text.trim().length === 0) {
      throw new Error('转录文本不能为空');
    }

    const stmt = this.db.prepare(`
      INSERT INTO transcriptions (
        text, raw_text, processed_text, confidence,
        language, duration, file_size
      ) VALUES (?, ?, ?, ?, ?, ?, ?)
    `);

    return stmt.run(
      text.trim(),
      data.raw_text || null,
      data.processed_text || null,
      data.confidence || 0,
      data.language || 'zh-CN',
      data.duration || 0,
      data.file_size || 0
    );
  }

  getTranscriptions(limit = 50, offset = 0) {
    const stmt = this.db.prepare(`
      SELECT * FROM transcriptions 
      ORDER BY created_at DESC 
      LIMIT ? OFFSET ?
    `);
    return stmt.all(limit, offset);
  }

  getTranscriptionById(id) {
    const stmt = this.db.prepare("SELECT * FROM transcriptions WHERE id = ?");
    return stmt.get(id);
  }

  deleteTranscription(id) {
    const stmt = this.db.prepare("DELETE FROM transcriptions WHERE id = ?");
    return stmt.run(id);
  }

  clearAllTranscriptions() {
    const stmt = this.db.prepare("DELETE FROM transcriptions");
    return stmt.run();
  }

  searchTranscriptions(query, limit = 50) {
    const stmt = this.db.prepare(`
      SELECT * FROM transcriptions 
      WHERE text LIKE ? OR raw_text LIKE ? OR processed_text LIKE ?
      ORDER BY created_at DESC 
      LIMIT ?
    `);
    const searchTerm = `%${query}%`;
    return stmt.all(searchTerm, searchTerm, searchTerm, limit);
  }

  getTranscriptionStats() {
    const totalStmt = this.db.prepare("SELECT COUNT(*) as total FROM transcriptions");
    const todayStmt = this.db.prepare(`
      SELECT COUNT(*) as today FROM transcriptions 
      WHERE date(created_at) = date('now')
    `);
    const weekStmt = this.db.prepare(`
      SELECT COUNT(*) as week FROM transcriptions 
      WHERE created_at >= date('now', '-7 days')
    `);

    return {
      total: totalStmt.get().total,
      today: todayStmt.get().today,
      week: weekStmt.get().week
    };
  }

  setSetting(key, value) {
    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO settings (key, value, updated_at) 
      VALUES (?, ?, CURRENT_TIMESTAMP)
    `);
    return stmt.run(key, JSON.stringify(value));
  }

  getSetting(key, defaultValue = null) {
    const stmt = this.db.prepare("SELECT value FROM settings WHERE key = ?");
    const result = stmt.get(key);
    
    if (result) {
      try {
        return JSON.parse(result.value);
      } catch (error) {
        return result.value;
      }
    }
    
    return defaultValue;
  }

  getAllSettings() {
    const stmt = this.db.prepare("SELECT key, value FROM settings");
    const rows = stmt.all();
    
    const settings = {};
    for (const row of rows) {
      try {
        settings[row.key] = JSON.parse(row.value);
      } catch (error) {
        settings[row.key] = row.value;
      }
    }
    
    return settings;
  }

  resetSettings() {
    const stmt = this.db.prepare("DELETE FROM settings");
    return stmt.run();
  }

  backup(backupPath) {
    if (!this.db) return false;
    
    try {
      this.db.backup(backupPath);
      return true;
    } catch (error) {
      if (this.logger && this.logger.error) {
        this.logger.error("数据库备份失败:", error);
      }
      return false;
    }
  }

  close() {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }
}

module.exports = DatabaseManager;
```

## /src/helpers/environment.js

```js path="/src/helpers/environment.js" 
const path = require("path");
const fs = require("fs");
const os = require("os");

class EnvironmentManager {
  constructor() {
    this.loadEnvironmentVariables();
  }

  loadEnvironmentVariables() {
    // 加载 .env 文件
    const envPath = path.join(process.cwd(), ".env");
    if (fs.existsSync(envPath)) {
      require("dotenv").config({ path: envPath });
    }
  }

  getAIConfig() {
    // AI配置现在通过控制面板设置,不再从环境变量读取
    return {
      apiKey: "",
      baseURL: "https://api.openai.com/v1",
      model: "gpt-3.5-turbo",
    };
  }

  getAudioConfig() {
    return {
      sampleRate: parseInt(process.env.AUDIO_SAMPLE_RATE) || 16000,
      channels: parseInt(process.env.AUDIO_CHANNELS) || 1,
      format: process.env.AUDIO_FORMAT || "wav",
    };
  }

  getFunASRConfig() {
    return {
      modelDir: process.env.FUNASR_MODEL_DIR || "./models",
      cacheDir: process.env.FUNASR_CACHE_DIR || "./cache",
      batchSize: parseInt(process.env.BATCH_SIZE) || 300,
      hotwords: process.env.HOTWORDS ? process.env.HOTWORDS.split(",") : [],
    };
  }

  getAppConfig() {
    return {
      debug: process.env.DEBUG === "true",
      logLevel: process.env.LOG_LEVEL || "info",
      language: process.env.LANGUAGE || "zh-CN",
      theme: process.env.THEME || "auto",
      windowAlwaysOnTop: process.env.WINDOW_ALWAYS_ON_TOP === "true",
      startMinimized: process.env.START_MINIMIZED === "true",
      globalHotkey: process.env.GLOBAL_HOTKEY || "CommandOrControl+Shift+Space",
    };
  }

  getDatabaseConfig() {
    return {
      path: process.env.DATABASE_PATH || "./data/transcriptions.db",
      backupEnabled: process.env.BACKUP_ENABLED !== "false",
      backupInterval: parseInt(process.env.BACKUP_INTERVAL) || 24,
    };
  }

  getProxyConfig() {
    return {
      http: process.env.HTTP_PROXY || "",
      https: process.env.HTTPS_PROXY || "",
    };
  }

  getPerformanceConfig() {
    return {
      maxRecordingDuration: parseInt(process.env.MAX_RECORDING_DURATION) || 300,
      maxTextLength: parseInt(process.env.MAX_TEXT_LENGTH) || 10000,
    };
  }

  getSystemInfo() {
    return {
      platform: process.platform,
      arch: process.arch,
      nodeVersion: process.version,
      electronVersion: process.versions.electron,
      chromeVersion: process.versions.chrome,
      osType: os.type(),
      osRelease: os.release(),
      totalMemory: os.totalmem(),
      freeMemory: os.freemem(),
      cpus: os.cpus().length,
      homeDir: os.homedir(),
      tmpDir: os.tmpdir(),
    };
  }

  isDevelopment() {
    return process.env.NODE_ENV === "development";
  }

  isProduction() {
    return process.env.NODE_ENV === "production";
  }

  getDataDirectory() {
    const appName = "蛐蛐";
    
    switch (process.platform) {
      case "win32":
        return path.join(os.homedir(), "AppData", "Roaming", appName);
      case "darwin":
        return path.join(os.homedir(), "Library", "Application Support", appName);
      case "linux":
        return path.join(os.homedir(), ".config", appName);
      default:
        return path.join(os.homedir(), `.${appName.toLowerCase()}`);
    }
  }

  ensureDataDirectory() {
    const dataDir = this.getDataDirectory();
    if (!fs.existsSync(dataDir)) {
      fs.mkdirSync(dataDir, { recursive: true });
    }
    return dataDir;
  }

  getLogDirectory() {
    const dataDir = this.ensureDataDirectory();
    const logDir = path.join(dataDir, "logs");
    if (!fs.existsSync(logDir)) {
      fs.mkdirSync(logDir, { recursive: true });
    }
    return logDir;
  }

  getCacheDirectory() {
    const dataDir = this.ensureDataDirectory();
    const cacheDir = path.join(dataDir, "cache");
    if (!fs.existsSync(cacheDir)) {
      fs.mkdirSync(cacheDir, { recursive: true });
    }
    return cacheDir;
  }

  getModelsDirectory() {
    const dataDir = this.ensureDataDirectory();
    const modelsDir = path.join(dataDir, "models");
    if (!fs.existsSync(modelsDir)) {
      fs.mkdirSync(modelsDir, { recursive: true });
    }
    return modelsDir;
  }

  validateEnvironment() {
    const issues = [];
    
    // 检查目录权限
    try {
      this.ensureDataDirectory();
    } catch (error) {
      issues.push(`无法创建数据目录: ${error.message}`);
    }
    
    // 检查系统要求
    const systemInfo = this.getSystemInfo();
    const nodeVersion = parseInt(systemInfo.nodeVersion.substring(1));
    if (nodeVersion < 18) {
      issues.push(`Node.js 版本过低: ${systemInfo.nodeVersion},需要 18+`);
    }
    
    return {
      valid: issues.length === 0,
      issues,
      systemInfo
    };
  }

  exportConfig() {
    return {
      ai: this.getAIConfig(),
      audio: this.getAudioConfig(),
      funasr: this.getFunASRConfig(),
      app: this.getAppConfig(),
      database: this.getDatabaseConfig(),
      proxy: this.getProxyConfig(),
      performance: this.getPerformanceConfig(),
      system: this.getSystemInfo(),
      directories: {
        data: this.getDataDirectory(),
        logs: this.getLogDirectory(),
        cache: this.getCacheDirectory(),
        models: this.getModelsDirectory(),
      }
    };
  }
}

module.exports = EnvironmentManager;
```

## /src/helpers/funasrManager.js

```js path="/src/helpers/funasrManager.js" 
const { spawn } = require("child_process");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const os = require("os");
const PythonInstaller = require("./pythonInstaller");
const { runCommand, TIMEOUTS } = require("../utils/process");

// 简单的全局缓存,避免频繁检查
let globalModelCheckCache = null;
let globalModelCheckTime = 0;
const GLOBAL_CACHE_TIME = 2000; // 减少到2秒缓存,确保及时更新

class FunASRManager {
  constructor(logger = null) {
    this.logger = logger || console; // 使用传入的logger或默认console
    this.pythonCmd = null; // 缓存 Python 可执行文件路径
    this.funasrInstalled = null; // 缓存安装状态
    this.isInitialized = false; // 跟踪启动初始化是否完成
    this.pythonInstaller = new PythonInstaller();
    this.modelsInitialized = false; // 跟踪模型是否已初始化
    this.initializationPromise = null; // 缓存初始化Promise
    this.serverProcess = null; // FunASR服务器进程
    this.serverReady = false; // 服务器是否就绪
    this.modelsDownloaded = null; // 缓存模型下载状态
    
    // 简化缓存
    this._cachedPythonEnv = null;
    this._lastEmbeddedCheck = null;
    
    // 模型配置
    this.modelConfigs = {
      "asr": {
        "name": "damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
        "cache_path": "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
        "expected_size": 840 * 1024 * 1024  // 840MB
      },
      "vad": {
        "name": "damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
        "cache_path": "speech_fsmn_vad_zh-cn-16k-common-pytorch",
        "expected_size": 1.6 * 1024 * 1024  // 1.6MB
      },
      "punc": {
        "name": "damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
        "cache_path": "punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
        "expected_size": 278 * 1024 * 1024  // 278MB
      }
    };
  }


  getFunASRServerPath() {
    // 获取FunASR服务器脚本路径
    if (process.env.NODE_ENV === "development") {
      return path.join(__dirname, "..", "..", "funasr_server.py");
    } else {
      return path.join(
        process.resourcesPath,
        "app.asar.unpacked",
        "funasr_server.py"
      );
    }
  }

  getEmbeddedPythonPath() {
    // 获取嵌入式Python路径
    if (process.env.NODE_ENV === "development") {
      return path.join(__dirname, "..", "..", "python", "bin", "python3.11");
    } else {
      return path.join(
        process.resourcesPath,
        "app.asar.unpacked",
        "python",
        "bin",
        "python3.11"
      );
    }
  }

  setupIsolatedEnvironment() {
    // 设置Python环境变量,根据实际使用的Python来决定
    const embeddedPythonPath = this.getEmbeddedPythonPath();
    const isUsingEmbedded = fs.existsSync(embeddedPythonPath);
    
    if (isUsingEmbedded) {
      // 使用嵌入式Python时设置完全隔离的环境变量
      const pythonHome = path.dirname(path.dirname(embeddedPythonPath));
      const sitePackages = path.join(pythonHome, 'lib', 'python3.11', 'site-packages');
      
      process.env.PYTHONHOME = pythonHome;
      process.env.PYTHONPATH = sitePackages;
      process.env.PYTHONDONTWRITEBYTECODE = '1';
      process.env.PYTHONIOENCODING = 'utf-8';
      process.env.PYTHONUNBUFFERED = '1';
      
      this.logger.info && this.logger.info('设置嵌入式Python环境', {
        PYTHONHOME: process.env.PYTHONHOME,
        PYTHONPATH: process.env.PYTHONPATH,
        pythonExecutable: embeddedPythonPath
      });
    } else {
      // 使用系统Python时,清除可能干扰的嵌入式Python环境变量
      delete process.env.PYTHONHOME;
      delete process.env.PYTHONPATH;
      
      // 设置基础环境变量
      process.env.PYTHONDONTWRITEBYTECODE = '1';
      process.env.PYTHONIOENCODING = 'utf-8';
      process.env.PYTHONUNBUFFERED = '1';
      
      this.logger.info && this.logger.info('设置系统Python环境', {
        note: '清除嵌入式Python环境变量,使用系统Python默认环境',
        pythonExecutable: this.pythonCmd || '未确定'
      });
    }
    
    // 清除可能干扰的系统Python环境变量
    delete process.env.PYTHONUSERBASE;
    delete process.env.PYTHONSTARTUP;
    delete process.env.VIRTUAL_ENV;
  }

  buildPythonEnvironment() {
    // 构建完整的Python环境变量,根据实际使用的Python路径来配置
    const embeddedPythonPath = this.getEmbeddedPythonPath();
    const isUsingEmbedded = fs.existsSync(embeddedPythonPath);
    
    // 缓存环境变量,避免重复构建和日志输出
    if (this._cachedPythonEnv && this._lastEmbeddedCheck === isUsingEmbedded) {
      return this._cachedPythonEnv;
    }
    
    let env = {
      ...process.env,
      // 基础Python环境变量
      PYTHONDONTWRITEBYTECODE: '1',
      PYTHONIOENCODING: 'utf-8',
      PYTHONUNBUFFERED: '1',
      
      // 设置用户数据目录用于日志
      ELECTRON_USER_DATA: require('electron').app.getPath('userData')
    };
    
    if (isUsingEmbedded) {
      // 使用嵌入式Python时的完整隔离环境
      const pythonHome = path.dirname(path.dirname(embeddedPythonPath));
      const sitePackages = path.join(pythonHome, 'lib', 'python3.11', 'site-packages');
      
      env.PYTHONHOME = pythonHome;
      env.PYTHONPATH = sitePackages;
      env.LD_LIBRARY_PATH = path.join(pythonHome, 'lib');
      env.DYLD_LIBRARY_PATH = path.join(pythonHome, 'lib'); // macOS
      
      // 只在首次构建或环境变化时记录日志
      if (!this._cachedPythonEnv || this._lastEmbeddedCheck !== isUsingEmbedded) {
        this.logger.info && this.logger.info('构建嵌入式Python环境变量', {
          PYTHONHOME: env.PYTHONHOME,
          PYTHONPATH: env.PYTHONPATH,
          LD_LIBRARY_PATH: env.LD_LIBRARY_PATH,
          DYLD_LIBRARY_PATH: env.DYLD_LIBRARY_PATH,
          pythonExecutable: embeddedPythonPath
        });
      }
    } else {
      // 使用系统Python时,清除可能干扰的嵌入式Python环境变量
      // 不设置PYTHONHOME和PYTHONPATH,让系统Python使用自己的环境
      if (!this._cachedPythonEnv || this._lastEmbeddedCheck !== isUsingEmbedded) {
        this.logger.info && this.logger.info('构建系统Python环境变量', {
          note: '使用系统Python默认环境',
          pythonExecutable: this.pythonCmd || '未确定'
        });
      }
    }
    
    // 清除可能干扰的系统Python环境变量
    delete env.PYTHONUSERBASE;
    delete env.PYTHONSTARTUP;
    delete env.VIRTUAL_ENV;
    
    // 缓存结果
    this._cachedPythonEnv = env;
    this._lastEmbeddedCheck = isUsingEmbedded;
    
    return env;
  }

  findDamoRoot(startDir, depth = 0, maxDepth = 5) {
    // 添加深度限制,避免在深层目录结构中搜索过久
    if (depth > maxDepth || !fs.existsSync(startDir)) {
      return null;
    }

    try {
      const entries = fs.readdirSync(startDir, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.isDirectory()) {
          const fullPath = path.join(startDir, entry.name);
          
          if (entry.name === 'damo') {
            // 检查是否包含至少一个目标模型子目录
            try {
              const models = fs.readdirSync(fullPath);
              const hasExpectedModel = models.some(m =>
                m.startsWith('speech_paraformer-') ||
                m.startsWith('speech_fsmn_vad-') ||
                m.startsWith('punc_ct-transformer-')
              );
              if (hasExpectedModel) {
                return fullPath;
              }
            } catch (error) {
              // 忽略无法读取的目录
              this.logger.debug && this.logger.debug('无法读取目录:', fullPath, error.message);
            }
          }
          
          // 递归继续查找 - 修复:添加 this 关键字
          const found = this.findDamoRoot(fullPath, depth + 1, maxDepth);
          if (found) return found;
        }
      }
    } catch (error) {
      // 处理权限错误或其他文件系统错误
      this.logger.debug && this.logger.debug('搜索目录时出错:', startDir, error.message);
    }
    
    return null;
  }

  /**
   * 获取模型缓存路径
   */
  getModelCachePath() {
    const baseCachePath =
      process.env.MODELSCOPE_CACHE || path.join(os.homedir(), '.cache', 'modelscope');

    // 可能的候选路径 - 添加 hub/models/damo 路径
    const candidates = [
      path.join(baseCachePath, 'damo'),
      path.join(baseCachePath, 'hub', 'damo'),
      path.join(baseCachePath, 'hub', 'models', 'damo'),  // 新增:支持 hub/models/damo 结构
      path.join(baseCachePath, 'models', 'damo'),
    ];

    // 先检查常见路径
    for (const candidate of candidates) {
      if (fs.existsSync(candidate)) {
        this.logger.info && this.logger.info('找到模型缓存路径:', candidate);
        return candidate;
      }
    }

    // 如果没找到,则递归搜索 - 修复:添加 this 关键字
    this.logger.info && this.logger.info('常见路径未找到,开始递归搜索:', baseCachePath);
    const found = this.findDamoRoot(baseCachePath);
    if (found) {
      this.logger.info && this.logger.info('递归搜索找到模型路径:', found);
      return found;
    }

    throw new Error(`未找到有效的 damo 模型目录,请检查 MODELSCOPE_CACHE 或模型安装路径`);
  }


  async checkModelFiles() {
    /**
     * 检查所有模型文件是否存在(使用简单缓存避免频繁检查)
     */
    const now = Date.now();
    
    // 使用全局缓存避免频繁检查,但如果服务器状态可能已变化则强制检查
    if (globalModelCheckCache &&
        (now - globalModelCheckTime) < GLOBAL_CACHE_TIME &&
        !this.serverReady) { // 如果服务器已就绪,允许重新检查
      return globalModelCheckCache;
    }
    
    try {
      const cachePath = this.getModelCachePath();
      this.logger.info && this.logger.info('检查模型缓存路径:', cachePath);
      
      if (!fs.existsSync(cachePath)) {
        this.logger.info && this.logger.info('模型缓存目录不存在');
        this.modelsDownloaded = false;
        const result = {
          success: true,
          models_downloaded: false,
          missing_models: ["asr", "vad", "punc"],
          details: {}
        };
        
        // 更新全局缓存
        globalModelCheckCache = result;
        globalModelCheckTime = now;
        return result;
      }
      
      const results = {};
      const missingModels = [];
      
      for (const [modelType, config] of Object.entries(this.modelConfigs)) {
        const modelDir = path.join(cachePath, config.cache_path);
        const modelFile = path.join(modelDir, "model.pt");
        
        if (fs.existsSync(modelFile)) {
          const stats = fs.statSync(modelFile);
          const fileSize = stats.size;
          const isComplete = fileSize >= config.expected_size * 0.95; // 允许5%误差
          
          results[modelType] = {
            exists: true,
            path: modelFile,
            size: fileSize,
            expected_size: config.expected_size,
            complete: isComplete
          };
          
          if (!isComplete) {
            missingModels.push(modelType);
          }
        } else {
          results[modelType] = {
            exists: false,
            path: modelFile,
            size: 0,
            expected_size: config.expected_size,
            complete: false
          };
          missingModels.push(modelType);
        }
      }
      
      const allDownloaded = missingModels.length === 0;
      this.modelsDownloaded = allDownloaded;
      
      this.logger.info && this.logger.info('模型检查完成:', {
        allDownloaded,
        missingModels,
        details: results
      });
      
      const result = {
        success: true,
        models_downloaded: allDownloaded,
        missing_models: missingModels,
        details: results
      };
      
      // 更新全局缓存
      globalModelCheckCache = result;
      globalModelCheckTime = now;
      return result;
      
    } catch (error) {
      this.logger.error && this.logger.error('检查模型文件失败:', error);
      this.modelsDownloaded = false;
      const result = {
        success: false,
        error: error.message,
        models_downloaded: false,
        missing_models: ["asr", "vad", "punc"],
        details: {}
      };
      
      // 错误情况下不缓存,允许重试
      return result;
    }
  }

  async getDownloadProgress() {
    /**
     * 获取模型下载进度
     */
    try {
      const cachePath = this.getModelCachePath();
      
      if (!fs.existsSync(cachePath)) {
        return {
          success: true,
          overall_progress: 0,
          models: {
            "asr": { progress: 0, downloaded: 0, total: this.modelConfigs.asr.expected_size },
            "vad": { progress: 0, downloaded: 0, total: this.modelConfigs.vad.expected_size },
            "punc": { progress: 0, downloaded: 0, total: this.modelConfigs.punc.expected_size }
          }
        };
      }
      
      const totalExpected = Object.values(this.modelConfigs).reduce((sum, config) => sum + config.expected_size, 0);
      let totalDownloaded = 0;
      const modelProgress = {};
      
      for (const [modelType, config] of Object.entries(this.modelConfigs)) {
        const modelDir = path.join(cachePath, config.cache_path);
        const modelFile = path.join(modelDir, "model.pt");
        
        let fileSize = 0;
        if (fs.existsSync(modelFile)) {
          const stats = fs.statSync(modelFile);
          fileSize = stats.size;
          totalDownloaded += fileSize;
        }
        
        const progress = Math.min(100, (fileSize / config.expected_size) * 100);
        
        modelProgress[modelType] = {
          progress: Math.round(progress * 10) / 10, // 保留1位小数
          downloaded: fileSize,
          total: config.expected_size
        };
      }
      
      const overallProgress = Math.min(100, (totalDownloaded / totalExpected) * 100);
      
      return {
        success: true,
        overall_progress: Math.round(overallProgress * 10) / 10,
        models: modelProgress
      };
      
    } catch (error) {
      this.logger.error && this.logger.error('获取下载进度失败:', error);
      return {
        success: false,
        error: error.message,
        overall_progress: 0,
        models: {}
      };
    }
  }

  getDownloadScriptPath() {
    /**
     * 获取下载脚本路径
     */
    if (process.env.NODE_ENV === "development") {
      return path.join(__dirname, "..", "..", "download_models.py");
    } else {
      return path.join(
        process.resourcesPath,
        "app.asar.unpacked",
        "download_models.py"
      );
    }
  }

  async downloadModels(progressCallback = null) {
    /**
     * 下载模型文件(使用独立的Python脚本并行下载)
     */
    try {
      this.logger.info && this.logger.info('开始下载FunASR模型...');
      
      // 先检查模型状态
      const checkResult = await this.checkModelFiles();
      if (checkResult.models_downloaded) {
        this.logger.info && this.logger.info('模型已存在,无需下载');
        return { success: true, message: "模型已存在,无需下载" };
      }
      
      const pythonCmd = await this.findPythonExecutable();
      const scriptPath = this.getDownloadScriptPath();
      
      this.logger.info && this.logger.info('启动模型下载脚本:', {
        pythonCmd,
        scriptPath,
        scriptExists: fs.existsSync(scriptPath)
      });
      
      if (!fs.existsSync(scriptPath)) {
        throw new Error(`下载脚本未找到: ${scriptPath}`);
      }
      
      return new Promise((resolve, reject) => {
        // 确保使用正确的Python环境
        const pythonEnv = this.buildPythonEnvironment();
        
        const downloadProcess = spawn(pythonCmd, [scriptPath], {
          stdio: ["pipe", "pipe", "pipe"],
          windowsHide: true,
          env: pythonEnv
        });
        
        let hasError = false;
        
        downloadProcess.stdout.on("data", (data) => {
          const lines = data.toString().split('\n').filter(line => line.trim());
          
          for (const line of lines) {
            try {
              const result = JSON.parse(line);
              
              if (result.error) {
                hasError = true;
                reject(new Error(result.error));
                return;
              }
              
              // 处理进度更新
              if (result.stage && progressCallback) {
                progressCallback({
                  stage: result.stage,
                  model: result.model,
                  progress: result.progress,
                  overall_progress: result.overall_progress,
                  completed: result.completed,
                  total: result.total
                });
              }
              
              // 处理最终结果
              if (result.success !== undefined) {
                if (result.success) {
                  this.modelsDownloaded = true;
                  resolve({ success: true, message: result.message || "模型下载完成" });
                } else {
                  hasError = true;
                  reject(new Error(result.error || "模型下载失败"));
                }
                return;
              }
              
            } catch (parseError) {
              // 忽略非JSON输出
              this.logger.debug && this.logger.debug('下载脚本非JSON输出:', line);
            }
          }
        });
        
        downloadProcess.stderr.on("data", (data) => {
          const errorOutput = data.toString();
          this.logger.error && this.logger.error('模型下载错误输出:', errorOutput);
        });
        
        downloadProcess.on("close", (code) => {
          if (!hasError) {
            if (code === 0) {
              this.modelsDownloaded = true;
              resolve({ success: true, message: "模型下载完成" });
            } else {
              reject(new Error(`模型下载进程退出,代码: ${code}`));
            }
          }
        });
        
        downloadProcess.on("error", (error) => {
          if (!hasError) {
            reject(new Error(`启动下载进程失败: ${error.message}`));
          }
        });
        
        // 设置超时(30分钟)
        setTimeout(() => {
          if (!hasError) {
            downloadProcess.kill();
            reject(new Error('模型下载超时'));
          }
        }, 30 * 60 * 1000);
      });
      
    } catch (error) {
      this.logger.error && this.logger.error('模型下载失败:', error);
      throw error;
    }
  }

  async restartServer() {
    /**
     * 重启FunASR服务器(用于模型下载完成后)
     */
    try {
      this.logger.info && this.logger.info('重启FunASR服务器...');
      
      // 停止现有服务器
      if (this.serverProcess) {
        await this._stopFunASRServer();
        this.logger.info && this.logger.info('已停止现有FunASR服务器');
      }
      
      // 重置状态并清除缓存
      this.serverReady = false;
      this.modelsInitialized = false;
      this.initializationPromise = null;
      this._clearModelCache();
      
      // 检查模型文件状态
      const modelStatus = await this.checkModelFiles();
      if (!modelStatus.models_downloaded) {
        throw new Error('模型文件未下载,无法启动服务器');
      }
      
      // 重新启动服务器
      this.initializationPromise = this._startFunASRServer();
      await this.initializationPromise;
      
      this.logger.info && this.logger.info('FunASR服务器重启完成');
      return { success: true, message: 'FunASR服务器重启成功' };
      
    } catch (error) {
      this.logger.error && this.logger.error('重启FunASR服务器失败:', error);
      return { success: false, error: error.message };
    }
  }

  _clearModelCache() {
    /**
     * 清除模型检查缓存
     */
    globalModelCheckCache = null;
    globalModelCheckTime = 0;
  }

  async initializeAtStartup() {
    try {
      this.logger.info && this.logger.info('FunASR管理器启动初始化开始');
      
      const pythonCmd = await this.findPythonExecutable();
      this.logger.info && this.logger.info('Python可执行文件找到', { pythonCmd });
      
      const funasrStatus = await this.checkFunASRInstallation();
      this.logger.info && this.logger.info('FunASR安装状态检查完成', funasrStatus);
      
      this.isInitialized = true;
      
      // 预初始化模型(异步进行,不阻塞启动)
      this.preInitializeModels();
      this.logger.info && this.logger.info('FunASR管理器启动初始化完成');
    } catch (error) {
      // FunASR 在启动时不可用不是关键问题
      this.logger.warn && this.logger.warn('FunASR启动初始化失败,但不影响应用启动', error);
      this.isInitialized = true;
    }
  }

  async preInitializeModels() {
    // 如果已经在初始化或已完成,直接返回
    if (this.initializationPromise) {
      return this.initializationPromise;
    }

    this.initializationPromise = this._startFunASRServer();
    return this.initializationPromise;
  }

  async _startFunASRServer() {
    try {
      this.logger.info && this.logger.info('启动FunASR服务器...');
      
      const status = await this.checkFunASRInstallation();
      if (!status.installed) {
        this.logger.warn && this.logger.warn('FunASR未安装,跳过服务器启动');
        return;
      }

      const pythonCmd = await this.findPythonExecutable();
      const serverPath = this.getFunASRServerPath();
      this.logger.info && this.logger.info('FunASR服务器配置', {
        pythonCmd,
        serverPath,
        serverExists: fs.existsSync(serverPath)
      });
      
      if (!fs.existsSync(serverPath)) {
        this.logger.error && this.logger.error('FunASR服务器脚本未找到,跳过服务器启动', { serverPath });
        return;
      }

      // 确保环境变量正确设置
      this.setupIsolatedEnvironment();
      
      // 构建完整的环境变量
      const pythonEnv = this.buildPythonEnvironment();

      return new Promise((resolve) => {
        this.logger.info && this.logger.info('启动FunASR Python进程', {
          command: pythonCmd,
          args: [serverPath],
          env: pythonEnv
        });
        const cachePath = this.getModelCachePath();
        // this.serverProcess = spawn(pythonCmd, [serverPath], {
        //   stdio: ["pipe", "pipe", "pipe"],
        //   windowsHide: true,
        //   env: pythonEnv // 使用完整的Python环境变量
        // });

        this.serverProcess = spawn(
          pythonCmd,
          [serverPath, "--damo-root", cachePath],   // <== 这里加上参数
          {
            stdio: ["pipe", "pipe", "pipe"],
            windowsHide: true,
            env: pythonEnv // 保持你原来的 Python 环境
          }
        );

        let initResponseReceived = false;

        this.serverProcess.stdout.on("data", (data) => {
          const lines = data.toString().split('\n').filter(line => line.trim());
          
          for (const line of lines) {
            this.logger.debug && this.logger.debug('FunASR服务器输出', { line });
            try {
              const result = JSON.parse(line);
              
              if (!initResponseReceived) {
                // 这是初始化响应
                initResponseReceived = true;
                if (result.success) {
                  this.serverReady = true;
                  this.modelsInitialized = true;
                  this._clearModelCache(); // 清除缓存,确保状态更新
                  this.logger.info && this.logger.info('FunASR服务器启动成功,模型已初始化');
                } else {
                  this.logger.error && this.logger.error('FunASR服务器初始化失败', result);
                }
                resolve();
              }
            } catch (parseError) {
              // 忽略非JSON输出,但记录到日志
              this.logger.debug && this.logger.debug('FunASR服务器非JSON输出', { line });
            }
          }
        });

        this.serverProcess.stderr.on("data", (data) => {
          const errorOutput = data.toString();
          this.logger.error && this.logger.error('FunASR服务器错误输出', { errorOutput });
          // 同时记录到FunASR专用日志
          if (this.logger.logFunASR) {
            this.logger.logFunASR('error', 'Python stderr', { errorOutput });
          }
        });

        this.serverProcess.on("close", (code) => {
          this.logger.warn && this.logger.warn('FunASR服务器进程退出', { code });
          this.serverProcess = null;
          this.serverReady = false;
          this.modelsInitialized = false;
          
          if (!initResponseReceived) {
            resolve();
          }
        });

        this.serverProcess.on("error", (error) => {
          this.logger.error && this.logger.error('FunASR服务器进程错误', error);
          this.serverProcess = null;
          this.serverReady = false;
          
          if (!initResponseReceived) {
            resolve();
          }
        });

        // 设置超时
        setTimeout(() => {
          if (!initResponseReceived) {
            this.logger.warn && this.logger.warn('FunASR服务器启动超时');
            if (this.serverProcess) {
              this.serverProcess.kill();
            }
            resolve();
          }
        }, 120000); // 2分钟超时
      });
    } catch (error) {
      this.logger.error && this.logger.error('启动FunASR服务器异常', error);
    }
  }

  async _sendServerCommand(command) {
    if (!this.serverProcess || !this.serverReady) {
      throw new Error('FunASR服务器未就绪');
    }

    return new Promise((resolve, reject) => {
      let responseReceived = false;
      
      const onData = (data) => {
        if (responseReceived) return;
        
        const lines = data.toString().split('\n').filter(line => line.trim());
        
        for (const line of lines) {
          try {
            const result = JSON.parse(line);
            responseReceived = true;
            this.serverProcess.stdout.removeListener('data', onData);
            resolve(result);
            return;
          } catch (parseError) {
            // 忽略非JSON输出
          }
        }
      };

      this.serverProcess.stdout.on('data', onData);
      
      // 发送命令
      this.serverProcess.stdin.write(JSON.stringify(command) + '\n');
      
      // 设置超时
      setTimeout(() => {
        if (!responseReceived) {
          responseReceived = true;
          this.serverProcess.stdout.removeListener('data', onData);
          reject(new Error('服务器响应超时'));
        }
      }, 60000); // 1分钟超时
    });
  }

  async _stopFunASRServer() {
    if (this.serverProcess) {
      try {
        // 发送退出命令
        await this._sendServerCommand({ action: 'exit' });
      } catch (error) {
        // 如果发送退出命令失败,直接杀死进程
        this.serverProcess.kill();
      }
      
      this.serverProcess = null;
      this.serverReady = false;
      this.modelsInitialized = false;
    }
  }

  async findPythonExecutable() {
    // 如果有缓存结果则返回
    if (this.pythonCmd) {
      return this.pythonCmd;
    }

    // 优先使用嵌入式Python(完全隔离策略)
    const embeddedPython = this.getEmbeddedPythonPath();
    
    this.logger.info && this.logger.info('检查嵌入式Python', {
      path: embeddedPython,
      exists: fs.existsSync(embeddedPython)
    });

    if (fs.existsSync(embeddedPython)) {
      try {
        // 设置隔离环境
        this.setupIsolatedEnvironment();
        
        // 验证嵌入式Python是否可用
        const version = await this.getPythonVersion(embeddedPython);
        if (this.isPythonVersionSupported(version)) {
          this.pythonCmd = embeddedPython;
          this.logger.info && this.logger.info('使用嵌入式Python', {
            path: embeddedPython,
            version: `${version.major}.${version.minor}`
          });
          return embeddedPython;
        }
      } catch (error) {
        this.logger.warn && this.logger.warn('嵌入式Python不可用', error);
      }
    }

    // 如果嵌入式Python不可用,在开发模式下回退到系统Python
    if (process.env.NODE_ENV === "development") {
      this.logger.warn && this.logger.warn('开发模式:回退到系统Python');
      return await this.findPythonExecutableWithFallback();
    }

    // 生产模式下不回退,确保完全隔离
    throw new Error(
      "嵌入式Python环境不可用。请重新安装应用或运行构建脚本准备Python环境。"
    );
  }

  async findPythonExecutableWithFallback() {
    // 保留原有的查找逻辑作为开发时的回退方案
    const projectRoot = path.join(__dirname, "..", "..");
      
    const possiblePaths = [
      // 优先使用 uv 虚拟环境中的 Python
      path.join(projectRoot, ".venv", "bin", "python3.11"),
      path.join(projectRoot, ".venv", "bin", "python3"),
      path.join(projectRoot, ".venv", "bin", "python"),
      // 然后尝试系统路径
      "python3.11",
      "python3",
      "python",
      "/usr/bin/python3.11",
      "/usr/bin/python3",
      "/usr/local/bin/python3.11",
      "/usr/local/bin/python3",
      "/opt/homebrew/bin/python3.11",
      "/opt/homebrew/bin/python3",
      "/usr/bin/python",
      "/usr/local/bin/python",
    ];

    for (const pythonPath of possiblePaths) {
      try {
        const version = await this.getPythonVersion(pythonPath);
        if (this.isPythonVersionSupported(version)) {
          this.pythonCmd = pythonPath;
          return pythonPath;
        }
      } catch (error) {
        continue;
      }
    }

    throw new Error(
      "未找到 Python 3.x。使用 installPython() 自动安装。"
    );
  }

  async getPythonVersion(pythonPath) {
    return new Promise((resolve) => {
      // 如果是嵌入式Python,使用完整的环境变量
      const isEmbedded = pythonPath === this.getEmbeddedPythonPath();
      const env = isEmbedded ? this.buildPythonEnvironment() : process.env;
      
      const testProcess = spawn(pythonPath, ["--version"], {
        env: env
      });
      let output = "";
      
      testProcess.stdout.on("data", (data) => output += data);
      testProcess.stderr.on("data", (data) => output += data);
      
      testProcess.on("close", (code) => {
        if (code === 0) {
          const match = output.match(/Python (\d+)\.(\d+)/i);
          resolve(match ? { major: +match[1], minor: +match[2] } : null);
        } else {
          resolve(null);
        }
      });
      
      testProcess.on("error", () => resolve(null));
    });
  }

  isPythonVersionSupported(version) {
    // 接受任何 Python 3.x 版本
    return version && version.major === 3;
  }

  async installPython(progressCallback = null) {
    try {
      // 清除缓存的 Python 命令,因为我们正在安装新的
      this.pythonCmd = null;
      
      const result = await this.pythonInstaller.installPython(progressCallback);
      
      // 安装后,尝试重新找到 Python
      try {
        await this.findPythonExecutable();
        return result;
      } catch (findError) {
        throw new Error("Python 已安装但在 PATH 中未找到。请重启应用程序。");
      }
      
    } catch (error) {
      this.logger.error && this.logger.error("Python 安装失败:", error);
      throw error;
    }
  }

  async checkPythonInstallation() {
    return await this.pythonInstaller.isPythonInstalled();
  }

  async checkFunASRInstallation() {
    // 如果有缓存结果则返回
    if (this.funasrInstalled !== null) {
      return this.funasrInstalled;
    }

    try {
      const pythonCmd = await this.findPythonExecutable();

      const result = await new Promise((resolve) => {
        // 确保使用正确的Python环境
        const pythonEnv = this.buildPythonEnvironment();
        
        const checkProcess = spawn(pythonCmd, [
          "-c",
          'import funasr; print("OK")',
        ], {
          env: pythonEnv
        });

        let output = "";
        let errorOutput = "";
        
        checkProcess.stdout.on("data", (data) => {
          output += data.toString();
        });
        
        checkProcess.stderr.on("data", (data) => {
          errorOutput += data.toString();
        });

        checkProcess.on("close", (code) => {
          if (code === 0 && output.includes("OK")) {
            resolve({ installed: true, working: true });
          } else {
            this.logger.error && this.logger.error('FunASR检查失败', {
              code,
              output,
              errorOutput
            });
            resolve({ installed: false, working: false, error: errorOutput || output });
          }
        });

        checkProcess.on("error", (error) => {
          resolve({ installed: false, working: false, error: error.message });
        });
      });

      this.funasrInstalled = result; // 缓存结果
      return result;
    } catch (error) {
      const errorResult = {
        installed: false,
        working: false,
        error: error.message,
      };
      this.funasrInstalled = errorResult;
      return errorResult;
    }
  }

  async upgradePip(pythonCmd) {
    return runCommand(pythonCmd, ["-m", "pip", "install", "--upgrade", "pip"], { timeout: TIMEOUTS.PIP_UPGRADE });
  }

  async installFunASR(progressCallback = null) {
    const pythonCmd = await this.findPythonExecutable();
    
    if (progressCallback) {
      progressCallback({ stage: "升级 pip...", percentage: 10 });
    }
    
    // 首先升级 pip 以避免版本问题
    try {
      await this.upgradePip(pythonCmd);
    } catch (error) {
      this.logger.warn && this.logger.warn("第一次 pip 升级尝试失败:", error.message);
      
      // 尝试用户安装方式升级 pip
      try {
        await runCommand(pythonCmd, ["-m", "pip", "install", "--user", "--upgrade", "pip"], { timeout: TIMEOUTS.PIP_UPGRADE });
      } catch (userError) {
        this.logger.warn && this.logger.warn("pip 升级完全失败,尝试继续");
      }
    }
    
    if (progressCallback) {
      progressCallback({ stage: "安装 FunASR...", percentage: 30 });
    }
    
    // 安装 FunASR 和相关依赖
    try {
      // 首先尝试常规安装
      await runCommand(pythonCmd, ["-m", "pip", "install", "-U", "funasr"], { timeout: TIMEOUTS.DOWNLOAD });
      
      if (progressCallback) {
        progressCallback({ stage: "安装 librosa...", percentage: 60 });
      }
      
      // 安装 librosa(音频处理库)
      await runCommand(pythonCmd, ["-m", "pip", "install", "-U", "librosa"], { timeout: TIMEOUTS.DOWNLOAD });
      
      if (progressCallback) {
        progressCallback({ stage: "安装完成!", percentage: 100 });
      }
      
      // 清除缓存状态
      this.funasrInstalled = null;
      
      return { success: true, message: "FunASR 安装成功" };
      
    } catch (error) {
      if (error.message.includes("Permission denied") || error.message.includes("access is denied")) {
        // 使用用户安装方式重试
        try {
          await runCommand(pythonCmd, ["-m", "pip", "install", "--user", "-U", "funasr"], { timeout: TIMEOUTS.DOWNLOAD });
          await runCommand(pythonCmd, ["-m", "pip", "install", "--user", "-U", "librosa"], { timeout: TIMEOUTS.DOWNLOAD });
          
          if (progressCallback) {
            progressCallback({ stage: "安装完成!", percentage: 100 });
          }
          
          this.funasrInstalled = null;
          return { success: true, message: "FunASR 安装成功(用户模式)" };
        } catch (userError) {
          throw new Error(`FunASR 安装失败: ${userError.message}`);
        }
      }
      
      // 增强常见问题的错误消息
      let message = error.message;
      if (message.includes("Microsoft Visual C++")) {
        message = "需要 Microsoft Visual C++ 构建工具。请安装 Visual Studio Build Tools。";
      } else if (message.includes("No matching distribution")) {
        message = "Python 版本不兼容。FunASR 需要 Python 3.8-3.11。";
      }
      
      throw new Error(message);
    }
  }

  async transcribeAudio(audioBlob, options = {}) {
    // 检查 FunASR 是否已安装
    const status = await this.checkFunASRInstallation();
    if (!status.installed) {
      throw new Error("FunASR 未安装。请先安装 FunASR。");
    }

    // 如果服务器还未就绪,等待初始化完成
    if (!this.serverReady && this.initializationPromise) {
      this.logger.info && this.logger.info('等待FunASR服务器就绪...');
      await this.initializationPromise;
    }

    const tempAudioPath = await this.createTempAudioFile(audioBlob);
    
    try {
      if (!this.serverReady) {
        throw new Error('FunASR服务器未就绪,请稍后重试');
      }
      
      // 使用服务器模式
      this.logger.info && this.logger.info('使用FunASR服务器模式进行转录');
      const result = await this._sendServerCommand({
        action: 'transcribe',
        audio_path: tempAudioPath,
        options: options
      });
      
      if (!result.success) {
        throw new Error(result.error || '转录失败');
      }
      
      return {
        success: true,
        text: result.text.trim(),
        raw_text: result.raw_text,
        confidence: result.confidence || 0.0,
        language: result.language || "zh-CN"
      };
    } catch (error) {
      throw error;
    } finally {
      await this.cleanupTempFile(tempAudioPath);
    }
  }

  async createTempAudioFile(audioBlob) {
    const tempDir = os.tmpdir();
    const filename = `funasr_audio_${crypto.randomUUID()}.wav`;
    const tempAudioPath = path.join(tempDir, filename);
    
    this.logger.info && this.logger.info('创建临时文件:', tempAudioPath);

    let buffer;
    if (audioBlob instanceof ArrayBuffer) {
      buffer = Buffer.from(audioBlob);
    } else if (audioBlob instanceof Uint8Array) {
      buffer = Buffer.from(audioBlob);
    } else if (typeof audioBlob === "string") {
      buffer = Buffer.from(audioBlob, "base64");
    } else if (audioBlob && audioBlob.buffer) {
      buffer = Buffer.from(audioBlob.buffer);
    } else {
      throw new Error(`不支持的音频数据类型: ${typeof audioBlob}`);
    }
    
    this.logger.debug && this.logger.debug('缓冲区创建,大小:', buffer.length);

    await fs.promises.writeFile(tempAudioPath, buffer);
    
    // 验证文件是否正确写入
    const stats = await fs.promises.stat(tempAudioPath);
    this.logger.info && this.logger.info('临时音频文件创建:', {
      path: tempAudioPath,
      size: stats.size,
      isFile: stats.isFile()
    });
    
    if (stats.size === 0) {
      throw new Error("音频文件为空");
    }
    
    return tempAudioPath;
  }


  async cleanupTempFile(tempAudioPath) {
    try {
      await fs.promises.unlink(tempAudioPath);
    } catch (cleanupError) {
      // 临时文件清理错误不是关键问题
    }
  }

  async checkStatus() {
    try {
      if (this.serverReady) {
        return await this._sendServerCommand({ action: 'status' });
      } else {
        // 检查FunASR是否已安装
        const installStatus = await this.checkFunASRInstallation();
        const modelStatus = await this.checkModelFiles();
        
        let error = "FunASR未安装";
        if (installStatus.installed) {
          if (!modelStatus.models_downloaded) {
            error = "模型文件未下载,请先下载模型";
          } else {
            error = "FunASR服务器正在启动中...";
          }
        }
        
        return {
          success: installStatus.installed && modelStatus.models_downloaded,
          error: error,
          installed: installStatus.installed,
          models_downloaded: modelStatus.models_downloaded,
          missing_models: modelStatus.missing_models || [],
          initializing: this.initializationPromise !== null
        };
      }
    } catch (error) {
      return {
        success: false,
        error: error.message,
        installed: false,
        models_downloaded: false
      };
    }
  }
}

module.exports = FunASRManager;
```

## /src/helpers/hotkeyManager.js

```js path="/src/helpers/hotkeyManager.js" 
const { globalShortcut } = require('electron');

class HotkeyManager {
  constructor(logger = null) {
    this.registeredHotkeys = new Map();
    this.f2ClickTimes = [];
    this.f2DoubleClickTimeout = 500; // 500ms内的两次点击算作双击
    this.onF2DoubleClick = null;
    this.isRecording = false;
    this.logger = logger;
    
    // 简化的热键防抖机制
    this.lastHotkeyTrigger = new Map();
    this.hotkeyDebounceTime = 200; // 200ms防抖时间,防止意外双击
  }

  /**
   * 注册F2双击热键
   * @param {Function} callback - 双击回调函数
   */
  registerF2DoubleClick(callback) {
    // 如果已经注册了F2,只更新回调函数,不重新注册
    if (this.registeredHotkeys.has('F2')) {
      if (this.logger && this.logger.info) {
        this.logger.info('F2热键已注册,更新回调函数');
      }
      this.onF2DoubleClick = callback;
      return true;
    }
    
    this.onF2DoubleClick = callback;
    
    // 注册F2单击监听
    const success = globalShortcut.register('F2', () => {
      this.handleF2Click();
    });

    if (success) {
      if (this.logger && this.logger.info) {
        this.logger.info('F2热键首次注册成功');
      }
      this.registeredHotkeys.set('F2', callback);
      return true;
    } else {
      if (this.logger && this.logger.error) {
        this.logger.error('F2热键注册失败');
      }
      return false;
    }
  }

  /**
   * 处理F2按键点击
   */
  handleF2Click() {
    const now = Date.now();
    this.f2ClickTimes.push(now);

    // 清理超过双击时间窗口的点击记录
    this.f2ClickTimes = this.f2ClickTimes.filter(
      time => now - time <= this.f2DoubleClickTimeout
    );

    // 检查是否为双击
    if (this.f2ClickTimes.length >= 2) {
      if (this.logger && this.logger.info) {
        this.logger.info('检测到F2双击');
      }
      this.handleF2DoubleClick();
      this.f2ClickTimes = []; // 清空点击记录
    }
  }

  /**
   * 处理F2双击事件
   */
  handleF2DoubleClick() {
    if (this.onF2DoubleClick) {
      // 根据当前状态决定动作
      const action = this.isRecording ? 'stop' : 'start';
      if (this.logger && this.logger.info) {
        this.logger.info(`F2双击 - ${action === 'start' ? '开始' : '停止'}录音,当前状态: ${this.isRecording}`);
      }
      
      this.onF2DoubleClick({
        action: action,
        currentState: this.isRecording
      });
      
      // 不在这里更新状态,让渲染进程来更新
    }
  }

  /**
   * 注册传统热键(如Cmd+Shift+Space)
   * @param {string} hotkey - 热键组合
   * @param {Function} callback - 回调函数
   */
  registerHotkey(hotkey, callback) {
    // 检查是否已经注册了相同的热键
    if (this.registeredHotkeys.has(hotkey)) {
      if (this.logger && this.logger.info) {
        this.logger.info(`热键 ${hotkey} 已注册,跳过重复注册`);
      }
      return true; // 返回成功,因为热键已经注册
    }

    // 创建带简单防抖的回调函数
    const debouncedCallback = () => {
      const now = Date.now();
      const lastTrigger = this.lastHotkeyTrigger.get(hotkey) || 0;
      
      // 简单防抖:防止意外的快速重复触发
      if (now - lastTrigger < this.hotkeyDebounceTime) {
        return;
      }
      
      this.lastHotkeyTrigger.set(hotkey, now);
      callback();
    };

    const success = globalShortcut.register(hotkey, debouncedCallback);
    
    if (success) {
      if (this.logger && this.logger.info) {
        this.logger.info(`热键 ${hotkey} 注册成功`);
      }
      this.registeredHotkeys.set(hotkey, debouncedCallback);
      return true;
    } else {
      if (this.logger && this.logger.error) {
        this.logger.error(`热键 ${hotkey} 注册失败`);
      }
      return false;
    }
  }

  /**
   * 注销热键
   * @param {string} hotkey - 热键组合
   */
  unregisterHotkey(hotkey) {
    if (this.registeredHotkeys.has(hotkey)) {
      globalShortcut.unregister(hotkey);
      this.registeredHotkeys.delete(hotkey);
      if (this.logger && this.logger.info) {
        this.logger.info(`热键 ${hotkey} 已注销`);
      }
      return true;
    }
    return false;
  }

  /**
   * 注销所有热键
   */
  unregisterAllHotkeys() {
    globalShortcut.unregisterAll();
    this.registeredHotkeys.clear();
    this.f2ClickTimes = [];
    if (this.logger && this.logger.info) {
      this.logger.info('所有热键已注销');
    }
  }

  /**
   * 获取已注册的热键列表
   */
  getRegisteredHotkeys() {
    return Array.from(this.registeredHotkeys.keys());
  }

  /**
   * 检查热键是否已注册
   * @param {string} hotkey - 热键组合
   */
  isHotkeyRegistered(hotkey) {
    return this.registeredHotkeys.has(hotkey);
  }

  /**
   * 设置录音状态(用于外部同步状态)
   * @param {boolean} isRecording - 录音状态
   */
  setRecordingState(isRecording) {
    this.isRecording = isRecording;
  }

  /**
   * 获取当前录音状态
   */
  getRecordingState() {
    return this.isRecording;
  }
}

module.exports = HotkeyManager;
```

## /src/helpers/ipcHandlers.js

```js path="/src/helpers/ipcHandlers.js" 
const { ipcMain } = require("electron");

class IPCHandlers {
  constructor(managers) {
    this.environmentManager = managers.environmentManager;
    this.databaseManager = managers.databaseManager;
    this.clipboardManager = managers.clipboardManager;
    this.funasrManager = managers.funasrManager;
    this.windowManager = managers.windowManager;
    this.hotkeyManager = managers.hotkeyManager;
    this.logger = managers.logger; // 添加logger引用
    
    // 跟踪F2热键注册状态
    this.f2RegisteredSenders = new Set();
    
    this.setupHandlers();
  }

  setupHandlers() {
    // 环境和配置相关
    ipcMain.handle("get-config", () => {
      return this.environmentManager.exportConfig();
    });

    ipcMain.handle("validate-environment", () => {
      return this.environmentManager.validateEnvironment();
    });

    // 录音相关
    ipcMain.handle("start-recording", async () => {
      // TODO: 实现录音开始功能
      return { success: true };
    });

    ipcMain.handle("stop-recording", async () => {
      // TODO: 实现录音停止功能
      return { success: true };
    });

    // Python 和 FunASR 相关
    ipcMain.handle("check-python", async () => {
      return await this.funasrManager.checkPythonInstallation();
    });

    ipcMain.handle("install-python", async (event, progressCallback) => {
      return await this.funasrManager.installPython((progress) => {
        event.sender.send("python-install-progress", progress);
      });
    });

    ipcMain.handle("check-funasr", async () => {
      return await this.funasrManager.checkFunASRInstallation();
    });

    ipcMain.handle("check-funasr-status", async () => {
      const status = await this.funasrManager.checkStatus();
      
      // 添加模型初始化状态信息
      return {
        ...status,
        models_initialized: this.funasrManager.modelsInitialized,
        server_ready: this.funasrManager.serverReady,
        is_initializing: this.funasrManager.initializationPromise !== null
      };
    });

    ipcMain.handle("install-funasr", async (event) => {
      return await this.funasrManager.installFunASR((progress) => {
        event.sender.send("funasr-install-progress", progress);
      });
    });

    ipcMain.handle("funasr-status", async () => {
      return await this.funasrManager.checkStatus();
    });

    // 模型文件管理
    ipcMain.handle("check-model-files", async () => {
      return await this.funasrManager.checkModelFiles();
    });

    ipcMain.handle("get-download-progress", async () => {
      return await this.funasrManager.getDownloadProgress();
    });

    ipcMain.handle("download-models", async (event) => {
      return await this.funasrManager.downloadModels((progress) => {
        event.sender.send("model-download-progress", progress);
      });
    });

    // AI文本处理
    ipcMain.handle("process-text", async (event, text, mode = 'optimize') => {
      return await this.processTextWithAI(text, mode);
    });

    ipcMain.handle("check-ai-status", async (event, testConfig = null) => {
      return await this.checkAIStatus(testConfig);
    });

    // 音频转录相关
    ipcMain.handle("transcribe-audio", async (event, audioData, options) => {
      return await this.funasrManager.transcribeAudio(audioData, options);
    });

    // 数据库相关
    ipcMain.handle("save-transcription", (event, data) => {
      return this.databaseManager.saveTranscription(data);
    });

    ipcMain.handle("get-transcriptions", (event, limit, offset) => {
      return this.databaseManager.getTranscriptions(limit, offset);
    });

    ipcMain.handle("get-transcription", (event, id) => {
      return this.databaseManager.getTranscriptionById(id);
    });

    ipcMain.handle("delete-transcription", (event, id) => {
      return this.databaseManager.deleteTranscription(id);
    });

    ipcMain.handle("search-transcriptions", (event, query, limit) => {
      return this.databaseManager.searchTranscriptions(query, limit);
    });

    ipcMain.handle("get-transcription-stats", () => {
      return this.databaseManager.getTranscriptionStats();
    });

    ipcMain.handle("clear-all-transcriptions", () => {
      return this.databaseManager.clearAllTranscriptions();
    });

    // 设置相关
    ipcMain.handle("get-setting", (event, key, defaultValue) => {
      return this.databaseManager.getSetting(key, defaultValue);
    });

    ipcMain.handle("set-setting", (event, key, value) => {
      return this.databaseManager.setSetting(key, value);
    });

    ipcMain.handle("get-all-settings", () => {
      return this.databaseManager.getAllSettings();
    });

    ipcMain.handle("get-settings", () => {
      return this.databaseManager.getAllSettings();
    });

    ipcMain.handle("save-setting", (event, key, value) => {
      return this.databaseManager.setSetting(key, value);
    });

    ipcMain.handle("reset-settings", () => {
      // TODO: 实现重置设置功能
      return this.databaseManager.resetSettings();
    });

    // 剪贴板相关
    ipcMain.handle("copy-text", async (event, text) => {
      try {
        return await this.clipboardManager.copyText(text);
      } catch (error) {
        this.logger.error("复制文本失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("paste-text", async (event, text) => {
      return this.clipboardManager.pasteText(text);
    });

    ipcMain.handle("insert-text-directly", async (event, text) => {
      try {
        return await this.clipboardManager.insertTextDirectly(text);
      } catch (error) {
        this.logger.error("直接插入文本失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("enable-macos-accessibility", async () => {
      try {
        if (process.platform === "darwin") {
          const result = await this.clipboardManager.enableMacOSAccessibility();
          return { success: result };
        }
        return { success: true, message: "非 macOS 平台,无需设置" };
      } catch (error) {
        this.logger.error("启用 macOS accessibility 失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("read-clipboard", async () => {
      try {
        const text = await this.clipboardManager.readClipboard();
        return { success: true, text };
      } catch (error) {
        this.logger.error("读取剪贴板失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("write-clipboard", async (event, text) => {
      try {
        return await this.clipboardManager.writeClipboard(text);
      } catch (error) {
        this.logger.error("写入剪贴板失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("get-clipboard-history", () => {
      // TODO: 实现剪贴板历史功能
      return [];
    });

    ipcMain.handle("clear-clipboard-history", () => {
      // TODO: 实现清除剪贴板历史功能
      return true;
    });

    // 窗口管理相关
    ipcMain.handle("hide-window", () => {
      if (this.windowManager.mainWindow) {
        this.windowManager.mainWindow.hide();
      }
      return true;
    });

    ipcMain.handle("show-window", () => {
      if (this.windowManager.mainWindow) {
        this.windowManager.mainWindow.show();
      }
      return true;
    });

    ipcMain.handle("minimize-window", () => {
      if (this.windowManager.mainWindow) {
        this.windowManager.mainWindow.minimize();
      }
      return true;
    });

    ipcMain.handle("close-window", () => {
      if (this.windowManager.mainWindow) {
        this.windowManager.mainWindow.close();
      }
      return true;
    });

    ipcMain.handle("show-control-panel", () => {
      this.windowManager.showControlPanel();
      return true;
    });

    ipcMain.handle("hide-control-panel", () => {
      this.windowManager.hideControlPanel();
      return true;
    });

    ipcMain.handle("open-control-panel", () => {
      this.windowManager.showControlPanel();
      return true;
    });

    ipcMain.handle("close-control-panel", () => {
      this.windowManager.hideControlPanel();
      return true;
    });

    ipcMain.handle("open-history-window", () => {
      this.windowManager.showHistoryWindow();
      return true;
    });

    ipcMain.handle("close-history-window", () => {
      this.windowManager.closeHistoryWindow();
      return true;
    });

    ipcMain.handle("hide-history-window", () => {
      this.windowManager.hideHistoryWindow();
      return true;
    });

    ipcMain.handle("open-settings-window", () => {
      this.windowManager.showSettingsWindow();
      return true;
    });

    ipcMain.handle("close-settings-window", () => {
      this.windowManager.closeSettingsWindow();
      return true;
    });

    ipcMain.handle("hide-settings-window", () => {
      this.windowManager.hideSettingsWindow();
      return true;
    });

    ipcMain.handle("close-app", () => {
      require("electron").app.quit();
    });

    // 热键管理 - 添加发送者跟踪机制
    this.hotkeyRegisteredSenders = new Set(); // 跟踪已注册热键的发送者
    
    ipcMain.handle("register-hotkey", (event, hotkey) => {
      try {
        if (this.hotkeyManager) {
          const senderId = event.sender.id;
          
          // 检查是否已经为这个发送者注册过热键
          if (this.hotkeyRegisteredSenders.has(senderId)) {
            this.logger.info(`发送者 ${senderId} 已注册过热键,跳过重复注册`);
            return { success: true };
          }
          
          const success = this.hotkeyManager.registerHotkey(hotkey, () => {
            // 只发送热键触发事件到主窗口,避免重复触发
            this.logger.info(`热键 ${hotkey} 被触发,发送事件到主窗口`);
            if (this.windowManager && this.windowManager.mainWindow && !this.windowManager.mainWindow.isDestroyed()) {
              this.windowManager.mainWindow.webContents.send("hotkey-triggered", { hotkey });
            }
          });
          
          if (success) {
            // 添加发送者到跟踪列表
            this.hotkeyRegisteredSenders.add(senderId);
            
            // 监听窗口关闭事件,清理注册记录
            event.sender.on('destroyed', () => {
              this.hotkeyRegisteredSenders.delete(senderId);
              this.logger.info(`清理发送者 ${senderId} 的热键注册记录`);
            });
            
            this.logger.info(`热键 ${hotkey} 注册成功,发送者: ${senderId}`);
          } else {
            this.logger.error(`热键 ${hotkey} 注册失败`);
          }
          
          return { success };
        }
        return { success: false, error: "热键管理器未初始化" };
      } catch (error) {
        this.logger.error("注册热键失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("unregister-hotkey", (event, hotkey) => {
      try {
        if (this.hotkeyManager) {
          const success = this.hotkeyManager.unregisterHotkey(hotkey);
          return { success };
        }
        return { success: false, error: "热键管理器未初始化" };
      } catch (error) {
        this.logger.error("注销热键失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("get-current-hotkey", () => {
      try {
        if (this.hotkeyManager) {
          const hotkeys = this.hotkeyManager.getRegisteredHotkeys();
          // 返回第一个非F2的热键,或默认热键
          const mainHotkey = hotkeys.find(key => key !== 'F2') || "CommandOrControl+Shift+Space";
          return mainHotkey;
        }
        return "CommandOrControl+Shift+Space";
      } catch (error) {
        this.logger.error("获取当前热键失败:", error);
        return "CommandOrControl+Shift+Space";
      }
    });

    // F2热键管理
    ipcMain.handle("register-f2-hotkey", (event) => {
      try {
        const senderId = event.sender.id;
        
        // 检查是否已经为这个发送者注册过F2热键
        if (this.f2RegisteredSenders.has(senderId)) {
          this.logger.info(`F2热键已为发送者 ${senderId} 注册过,跳过重复注册`);
          return { success: true };
        }
        
        if (this.hotkeyManager) {
          // 只有在没有任何发送者注册时才注册热键
          const isFirstRegistration = this.f2RegisteredSenders.size === 0;
          
          if (isFirstRegistration) {
            const success = this.hotkeyManager.registerF2DoubleClick((data) => {
              // 发送F2双击事件到所有注册的渲染进程
              this.logger.info("发送F2双击事件到渲染进程:", data);
              this.f2RegisteredSenders.forEach(id => {
                const window = require("electron").BrowserWindow.getAllWindows().find(w => w.webContents.id === id);
                if (window && !window.isDestroyed()) {
                  window.webContents.send("f2-double-click", data);
                }
              });
            });
            
            if (!success) {
              return { success: false, error: "F2热键注册失败" };
            }
          }
          
          // 添加发送者到跟踪列表
          this.f2RegisteredSenders.add(senderId);
          
          // 监听窗口关闭事件,清理注册记录
          event.sender.on('destroyed', () => {
            this.f2RegisteredSenders.delete(senderId);
            this.logger.info(`清理发送者 ${senderId} 的F2热键注册记录`);

            // 如果没有发送者了,注销热键
            if (this.f2RegisteredSenders.size === 0) {
              this.hotkeyManager.unregisterHotkey('F2');
              this.logger.info('所有发送者都已注销,注销F2热键');
            }
          });
          
          return { success: true };
        }
        return { success: false, error: "热键管理器未初始化" };
      } catch (error) {
        this.logger.error("注册F2热键失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("unregister-f2-hotkey", (event) => {
      try {
        const senderId = event.sender.id;
        
        if (this.hotkeyManager && this.f2RegisteredSenders.has(senderId)) {
          this.f2RegisteredSenders.delete(senderId);
          
          // 如果没有其他发送者注册F2热键,则注销热键
          if (this.f2RegisteredSenders.size === 0) {
            const success = this.hotkeyManager.unregisterHotkey('F2');
            this.logger.info('所有发送者都已注销,注销F2热键');
            return { success };
          } else {
            this.logger.info(`发送者 ${senderId} 已注销,但还有其他发送者注册了F2热键`);
            return { success: true };
          }
        }
        return { success: false, error: "热键管理器未初始化或未注册" };
      } catch (error) {
        this.logger.error("注销F2热键失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("set-recording-state", (event, isRecording) => {
      try {
        if (this.hotkeyManager) {
          this.hotkeyManager.setRecordingState(isRecording);
          return { success: true };
        }
        return { success: false, error: "热键管理器未初始化" };
      } catch (error) {
        this.logger.error("设置录音状态失败:", error);
        return { success: false, error: error.message };
      }
    });

    ipcMain.handle("get-recording-state", () => {
      try {
        if (this.hotkeyManager) {
          const isRecording = this.hotkeyManager.getRecordingState();
          return { success: true, isRecording };
        }
        return { success: false, error: "热键管理器未初始化" };
      } catch (error) {
        this.logger.error("获取录音状态失败:", error);
        return { success: false, error: error.message };
      }
    });

    // 文件操作
    ipcMain.handle("export-transcriptions", (event, format) => {
      // TODO: 实现导出转录功能
      return { success: true, path: "" };
    });

    ipcMain.handle("import-settings", () => {
      // TODO: 实现导入设置功能
      return { success: true };
    });

    ipcMain.handle("export-settings", () => {
      // TODO: 实现导出设置功能
      return { success: true, path: "" };
    });

    // 文件系统相关
    ipcMain.handle("show-item-in-folder", (event, fullPath) => {
      require("electron").shell.showItemInFolder(fullPath);
    });

    ipcMain.handle("open-external", (event, url) => {
      require("electron").shell.openExternal(url);
    });

    // 系统信息
    ipcMain.handle("get-system-info", () => {
      return {
        platform: process.platform,
        arch: process.arch,
        nodeVersion: process.version,
        electronVersion: process.versions.electron
      };
    });

    ipcMain.handle("check-permissions", async () => {
      try {
        // 检查辅助功能权限
        const hasAccessibility = await this.clipboardManager.checkAccessibilityPermissions();
        
        return {
          microphone: true, // 麦克风权限由前端检查
          accessibility: hasAccessibility
        };
      } catch (error) {
        this.logger.error("检查权限失败:", error);
        return {
          microphone: false,
          accessibility: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("request-permissions", async () => {
      try {
        // 对于辅助功能权限,我们只能引导用户手动授予
        // 这里可以打开系统设置页面
        if (process.platform === "darwin") {
          this.clipboardManager.openSystemSettings();
        }
        return { success: true };
      } catch (error) {
        this.logger.error("请求权限失败:", error);
        return { success: false, error: error.message };
      }
    });

    // 测试辅助功能权限
    ipcMain.handle("test-accessibility-permission", async () => {
      try {
        // 使用测试文本检查权限
        await this.clipboardManager.pasteText("蛐蛐权限测试");
        return { success: true, message: "辅助功能权限测试成功" };
      } catch (error) {
        this.logger.error("辅助功能权限测试失败:", error);
        return { success: false, error: error.message };
      }
    });

    // 打开系统权限设置
    ipcMain.handle("open-system-permissions", () => {
      try {
        if (process.platform === "darwin") {
          this.clipboardManager.openSystemSettings();
          return { success: true };
        } else {
          return { success: false, error: "当前平台不支持自动打开权限设置" };
        }
      } catch (error) {
        this.logger.error("打开系统权限设置失败:", error);
        return { success: false, error: error.message };
      }
    });

    // 应用信息
    ipcMain.handle("get-app-version", () => {
      return require("electron").app.getVersion();
    });

    ipcMain.handle("get-app-path", (event, name) => {
      return require("electron").app.getPath(name);
    });

    ipcMain.handle("check-for-updates", () => {
      // TODO: 实现更新检查功能
      return { hasUpdate: false };
    });

    // 调试和日志
    ipcMain.handle("log", (event, level, message, data) => {
      this.logger[level](`[渲染进程] ${message}`, data || "");
      return true;
    });

    ipcMain.handle("get-debug-info", () => {
      return {
        platform: process.platform,
        arch: process.arch,
        nodeVersion: process.version,
        electronVersion: process.versions.electron,
        appVersion: require("electron").app.getVersion()
      };
    });

    // 保持向后兼容性
    ipcMain.handle("log-message", (event, level, message, data) => {
      this.logger[level](`[渲染进程] ${message}`, data || "");
      return true;
    });

    // 中文特定功能
    ipcMain.handle("detect-language", (event, text) => {
      // TODO: 实现语言检测功能
      return { language: "zh-CN", confidence: 0.95 };
    });

    ipcMain.handle("segment-chinese", (event, text) => {
      // TODO: 实现中文分词功能
      return { segments: text.split("") };
    });

    ipcMain.handle("add-punctuation", (event, text) => {
      // TODO: 实现标点符号添加功能
      return { text: text };
    });

    // 音频处理
    ipcMain.handle("convert-audio-format", (event, audioData, targetFormat) => {
      // TODO: 实现音频格式转换功能
      return { success: true, data: audioData };
    });

    ipcMain.handle("enhance-audio", (event, audioData) => {
      // TODO: 实现音频增强功能
      return { success: true, data: audioData };
    });

    // 模型管理 - 更新为实际功能
    ipcMain.handle("download-model", async (event, modelName) => {
      // 使用统一的模型下载功能
      return await this.funasrManager.downloadModels((progress) => {
        event.sender.send("model-download-progress", progress);
      });
    });

    ipcMain.handle("get-available-models", () => {
      // 返回FunASR支持的模型列表
      return {
        models: [
          {
            name: "paraformer-large",
            displayName: "Paraformer Large (ASR)",
            type: "asr",
            size: "840MB",
            description: "大型中文语音识别模型"
          },
          {
            name: "fsmn-vad",
            displayName: "FSMN VAD",
            type: "vad",
            size: "1.6MB",
            description: "语音活动检测模型"
          },
          {
            name: "ct-transformer-punc",
            displayName: "CT Transformer (标点)",
            type: "punc",
            size: "278MB",
            description: "标点符号恢复模型"
          }
        ]
      };
    });

    ipcMain.handle("get-current-model", async () => {
      const status = await this.funasrManager.checkStatus();
      return {
        model: "paraformer-large",
        status: status.models_downloaded ? "ready" : "not_downloaded",
        details: status
      };
    });

    ipcMain.handle("switch-model", (event, modelName) => {
      // FunASR目前使用固定模型组合,暂不支持切换
      return {
        success: false,
        error: "FunASR使用固定模型组合,暂不支持切换单个模型"
      };
    });

    // 性能监控
    ipcMain.handle("get-performance-stats", () => {
      // TODO: 实现性能统计功能
      return { stats: {} };
    });

    ipcMain.handle("clear-performance-stats", () => {
      // TODO: 实现清除性能统计功能
      return { success: true };
    });

    // 错误报告
    ipcMain.handle("report-error", (event, error) => {
      this.logger.error("渲染进程错误:", error);
      // TODO: 实现错误报告功能
      return true;
    });

    // 开发工具
    if (process.env.NODE_ENV === "development") {
      ipcMain.handle("open-dev-tools", (event) => {
        const window = require("electron").BrowserWindow.fromWebContents(event.sender);
        if (window) {
          window.webContents.openDevTools();
        }
      });

      ipcMain.handle("reload-window", (event) => {
        const window = require("electron").BrowserWindow.fromWebContents(event.sender);
        if (window) {
          window.reload();
        }
      });
    }

    // 日志和调试相关
    ipcMain.handle("get-app-logs", (event, lines = 100) => {
      try {
        if (this.logger && this.logger.getRecentLogs) {
          return {
            success: true,
            logs: this.logger.getRecentLogs(lines)
          };
        }
        return {
          success: false,
          error: "日志管理器不可用"
        };
      } catch (error) {
        this.logger.error("获取应用日志失败:", error);
        return {
          success: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("get-funasr-logs", (event, lines = 100) => {
      try {
        if (this.logger && this.logger.getFunASRLogs) {
          return {
            success: true,
            logs: this.logger.getFunASRLogs(lines)
          };
        }
        return {
          success: false,
          error: "日志管理器不可用"
        };
      } catch (error) {
        this.logger.error("获取FunASR日志失败:", error);
        return {
          success: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("get-log-file-path", () => {
      try {
        if (this.logger && this.logger.getLogFilePath) {
          return {
            success: true,
            appLogPath: this.logger.getLogFilePath(),
            funasrLogPath: this.logger.getFunASRLogFilePath()
          };
        }
        return {
          success: false,
          error: "日志管理器不可用"
        };
      } catch (error) {
        this.logger.error("获取日志文件路径失败:", error);
        return {
          success: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("open-log-file", (event, logType = 'app') => {
      try {
        if (this.logger) {
          const logPath = logType === 'funasr'
            ? this.logger.getFunASRLogFilePath()
            : this.logger.getLogFilePath();
          
          require("electron").shell.showItemInFolder(logPath);
          return { success: true };
        }
        return {
          success: false,
          error: "日志管理器不可用"
        };
      } catch (error) {
        this.logger.error("打开日志文件失败:", error);
        return {
          success: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("get-system-debug-info", () => {
      try {
        const debugInfo = {
          system: {
            platform: process.platform,
            arch: process.arch,
            nodeVersion: process.version,
            electronVersion: process.versions.electron,
            appVersion: require("electron").app.getVersion()
          },
          environment: {
            NODE_ENV: process.env.NODE_ENV,
            PATH: process.env.PATH,
            PYTHON_PATH: process.env.PYTHON_PATH,
            AI_API_KEY: '通过控制面板设置',
            AI_BASE_URL: '通过控制面板设置',
            AI_MODEL: '通过控制面板设置'
          },
          funasrStatus: {
            isInitialized: this.funasrManager.isInitialized,
            modelsInitialized: this.funasrManager.modelsInitialized,
            serverReady: this.funasrManager.serverReady,
            pythonCmd: this.funasrManager.pythonCmd
          }
        };

        if (this.logger && this.logger.getSystemInfo) {
          debugInfo.loggerInfo = this.logger.getSystemInfo();
        }

        return {
          success: true,
          debugInfo
        };
      } catch (error) {
        this.logger.error("获取系统调试信息失败:", error);
        return {
          success: false,
          error: error.message
        };
      }
    });

    ipcMain.handle("test-python-environment", async () => {
      try {
        this.logger && this.logger.info && this.logger.info('开始测试Python环境');
        
        const pythonCmd = await this.funasrManager.findPythonExecutable();
        const funasrStatus = await this.funasrManager.checkFunASRInstallation();
        
        const testResult = {
          success: true,
          pythonCmd,
          funasrStatus,
          timestamp: new Date().toISOString()
        };

        this.logger && this.logger.info && this.logger.info('Python环境测试完成', testResult);
        
        return testResult;
      } catch (error) {
        const errorResult = {
          success: false,
          error: error.message,
          timestamp: new Date().toISOString()
        };

        this.logger && this.logger.error && this.logger.error('Python环境测试失败', errorResult);
        
        return errorResult;
      }
    });

    ipcMain.handle("restart-funasr-server", async () => {
      try {
        this.logger && this.logger.info && this.logger.info('手动重启FunASR服务器');
        
        // 使用新的restartServer方法
        const result = await this.funasrManager.restartServer();
        
        return result;
      } catch (error) {
        this.logger && this.logger.error && this.logger.error('重启FunASR服务器失败', error);
        return {
          success: false,
          error: error.message
        };
      }
    });
  }

  // AI文本处理方法
  async processTextWithAI(text, mode = 'optimize') {
    try {
      // 从数据库设置中获取API密钥
      const apiKey = await this.databaseManager.getSetting('ai_api_key');
      if (!apiKey) {
        return {
          success: false,
          error: '请先在设置页面配置AI API密钥'
        };
      }

      const prompts = {
        format: `请将以下文本进行格式化,添加适当的段落分隔,使其更易阅读:\n\n${text}`,
        correct: `请纠正以下文本中的语法错误、错别字和语音识别错误,保持原意不变:\n\n${text}`,
        optimize: `# 角色与目标
你是一个专业的语音转录文本优化助手,任务是对由ASR(自动语音识别)生成的初步文本进行精细的、最小化的润色。你的核心目标是去除言语组织过程中的干扰性噪音,同时100%保留说话人的原始意图、个人风格和口语习惯。

# 核心原则
- **最小化修改**:只处理明确的、非内容性的言语错误。
- **保留原貌**:最大限度地保留用户的原始用词、句式和语气。
- **可读性优先**:在不改变原意的前提下,提升文本的流畅性和可读性。
- **歧义时保守**:当不确定一个词或一句话是否需要修改时,必须选择保持原样。

# 明确的优化指令 (Do's)
1.  **纠正明显的拼写和语法错误**:修正同音错字、标点误用、以及基础的语法搭配错误(如主谓不一致)。
2.  **移除无意义的填充词**:删除如"呃"、"嗯"、"啊这"、"那个"、"内个"、"然后那个"、"就是说"等在思考或停顿时使用的、不承载实际信息的词语。
3.  **处理重复与口吃**:合并无意义的重复词语。
    -   例子1: "我我我觉得" -> "我觉得"
    -   例子2: "这个这个方案" -> "这个方案"
4.  **整合自我修正**:当用户明确表达了修正意图时,保留修正后的最终内容,并移除被修正的错误部分。
    -   例子1: "会议定在周三,呃不对,是周四" -> "会议定在周四"
    -   例子2: "他的名字是小明,哦我想起来了,是小强" -> "他的名字是小强"

# 严格的禁止项 (Don'ts)
1.  **禁止风格转换**:绝不能将口语化的表达(如"录个影"、"蛮不错")替换为更书面化的词语(如"录制视频"、"非常好")。
2.  **禁止替换用词**:除非是明显的错别字,否则不能改变用户的任何用词选择。
3.  **禁止改变句式**:不能为了"优化"而重组用户的句子结构,例如将主动句改为被动句。
4.  **禁止增删情感或语气词**:必须保留所有表达情感和语气的词,如"啊"、"呀"、"呢"、"吧"、"嘛"、"哦"、"喔"等。注意区分它们和第2条指令中提到的"无意义填充词"。
5.  **禁止主观臆断**:不能添加任何原始文本中不存在的信息,或基于猜测去"完善"句子。

原始文本:
\`\`\`
${text}
\`\`\`

# 输出格式
- **输出**: 直接返回优化后的文本,不要包含任何解释、前言或总结。`,
        optimize_long: `# 角色与目标
你是一个专业的长文本整理助手,专门处理语音转录的长段内容。你的任务是清理口语化的思考过程,并对内容进行逻辑分段,让文本更加清晰易读。

# 处理重点
这是一段较长的语音转录内容,通常包含完整的思考过程。你需要:

## 1. 清理口语化的思考过程
- **去除思考痕迹**:删除"然后"、"就是说"、"其实"、"比如说"、"怎么说呢"、"应该是"等思考过程中的冗余表达
- **处理话题跳转**:整理"对了"、"还有"、"另外"等突然转换话题的表达
- **清理重复表述**:去除同一观点的多次重复表达,保留最清晰的一次
- **整合修正表达**:当有"不对,我的意思是"、"更准确地说"等自我纠正时,保留最终的正确表达

## 2. 智能分段
- **识别逻辑转折点**:在话题转换、观点变化、举例说明等地方进行分段
- **保持逻辑完整性**:确保每段都有完整的逻辑表达
- **适度分段**:避免过短或过长的段落,保持阅读节奏

## 3. 保持原意和自然性
- **不改变表达风格**:保持原有的用词习惯和表达方式
- **不添加新内容**:绝不添加原文中没有的信息
- **保留重要细节**:确保例子、数据、具体描述都得到保留

原始文本:
\`\`\`
${text}
\`\`\`

请直接返回清理后并分段的文本,不要包含任何解释或说明。`,
        summarize: `请总结以下文本的主要内容,提取关键信息:\n\n${text}`,
        enhance: `请对以下文本进行内容优化:

**优化要求:**
1. **严格保持原意和语义不变**
2. 纠正明显的用词错误和语法问题
3. 优化表达方式,使语言更加准确和流畅
4. 可以调整标点符号以提升文本质量
5. 保留原文的语言风格

**注意事项:**
- 对于诗词、成语、俗语等固定表达,请保持原样
- 宁可保守处理,也不要过度修改

原始文本:
${text}

请直接返回优化后的文本,不需要解释过程。`
      };

      const baseUrl = await this.databaseManager.getSetting('ai_base_url') || 'https://api.openai.com/v1';
      const model = await this.databaseManager.getSetting('ai_model') || 'gpt-3.5-turbo';

      const requestData = {
        model: model,
        messages: [
          {
            role: 'user',
            content: prompts[mode] || prompts.optimize
          }
        ],
        temperature: 0.3,
        max_tokens: 2000,
        stream: false
      };

      this.logger.info('AI文本处理请求:', {
        baseUrl,
        model,
        mode,
        inputText: text.substring(0, 100) + (text.length > 100 ? '...' : ''),
        requestData
      });

      const response = await fetch(`${baseUrl}/chat/completions`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(requestData)
      });

      if (!response.ok) {
        const errorText = await response.text();
        let errorData = { error: response.statusText };
        try {
          errorData = JSON.parse(errorText);
        } catch {
          errorData = { error: errorText || response.statusText };
        }
        throw new Error(errorData.error?.message || errorData.error || `API error: ${response.status}`);
      }

      const data = await response.json();

      this.logger.info('AI文本处理响应:', {
        status: response.status,
        data: data,
        usage: data.usage
      });

      if (data.choices && data.choices.length > 0) {
        const result = {
          success: true,
          text: data.choices[0].message.content.trim(),
          usage: data.usage,
          model: model
        };
        
        this.logger.info('AI文本处理结果:', {
          originalText: text.substring(0, 100) + (text.length > 100 ? '...' : ''),
          optimizedText: result.text.substring(0, 100) + (result.text.length > 100 ? '...' : ''),
          usage: result.usage
        });
        
        return result;
      } else {
        this.logger.error('AI API返回数据格式错误:', response.data);
        return {
          success: false,
          error: 'AI API返回数据格式错误'
        };
      }
    } catch (error) {
      this.logger.error('AI文本处理失败:', error);
      
      let errorMessage = '文本处理失败';
      if (error.response) {
        // API错误响应
        if (error.response.status === 401) {
          errorMessage = 'API密钥无效,请检查配置';
        } else if (error.response.status === 429) {
          errorMessage = 'API调用频率超限,请稍后重试';
        } else if (error.response.status === 500) {
          errorMessage = 'AI服务器错误,请稍后重试';
        } else {
          errorMessage = `API错误: ${error.response.status}`;
        }
      } else if (error.code === 'ECONNABORTED') {
        errorMessage = '请求超时,请检查网络连接';
      } else if (error.code === 'ENOTFOUND') {
        errorMessage = '无法连接到AI服务器,请检查网络';
      } else {
        errorMessage = error.message || '未知错误';
      }

      return {
        success: false,
        error: errorMessage
      };
    }
  }

  // 检查AI状态
  async checkAIStatus(testConfig = null) {
    try {
      this.logger.info('开始测试AI配置...', testConfig ? '使用临时配置' : '使用已保存配置');
      
      // 如果提供了测试配置,使用测试配置;否则使用已保存的配置
      let apiKey, baseUrl, model;
      
      if (testConfig) {
        apiKey = testConfig.ai_api_key;
        baseUrl = testConfig.ai_base_url || 'https://api.openai.com/v1';
        model = testConfig.ai_model || 'gpt-3.5-turbo';
        this.logger.info('使用临时测试配置:', { baseUrl, model, apiKeyLength: apiKey?.length || 0 });
      } else {
        apiKey = await this.databaseManager.getSetting('ai_api_key');
        baseUrl = await this.databaseManager.getSetting('ai_base_url') || 'https://api.openai.com/v1';
        model = await this.databaseManager.getSetting('ai_model') || 'gpt-3.5-turbo';
        this.logger.info('使用已保存配置:', { baseUrl, model, apiKeyLength: apiKey?.length || 0 });
      }
      
      if (!apiKey) {
        this.logger.warn('AI测试失败: 未配置API密钥');
        return {
          available: false,
          error: '未配置API密钥',
          details: '请输入AI API密钥'
        };
      }
      
      this.logger.info('AI配置信息:', {
        baseUrl: baseUrl,
        model: model,
        apiKeyLength: apiKey.length
      });
      
      // 发送一个更有意义的测试请求
      const testMessage = '请回复"测试成功"来确认AI服务正常工作';
      const requestData = {
        model: model,
        messages: [
          {
            role: 'user',
            content: testMessage
          }
        ],
        max_tokens: 50,
        temperature: 0.1
      };

      this.logger.info('发送AI测试请求:', requestData);

      const response = await fetch(`${baseUrl}/chat/completions`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(requestData)
      });

      this.logger.info('AI API响应状态:', response.status);

      if (!response.ok) {
        const errorText = await response.text();
        this.logger.error('AI API错误响应:', errorText);
        
        let errorData = { error: response.statusText };
        try {
          errorData = JSON.parse(errorText);
        } catch {
          errorData = { error: errorText || response.statusText };
        }
        
        let errorMessage = errorData.error?.message || errorData.error || `HTTP ${response.status}`;
        if (response.status === 401) {
          errorMessage = 'API密钥无效或已过期';
        } else if (response.status === 403) {
          errorMessage = 'API密钥权限不足';
        } else if (response.status === 429) {
          errorMessage = 'API调用频率超限';
        } else if (response.status === 500) {
          errorMessage = 'AI服务器内部错误';
        }
        
        throw new Error(errorMessage);
      }

      const data = await response.json();
      this.logger.info('AI API成功响应:', data);

      if (!data.choices || data.choices.length === 0) {
        throw new Error('AI API返回格式异常:缺少choices字段');
      }

      const aiResponse = data.choices[0].message?.content || '';
      this.logger.info('AI回复内容:', aiResponse);

      return {
        available: true,
        model: model,
        status: 'connected',
        response: aiResponse,
        usage: data.usage,
        details: `成功连接到 ${model},响应时间正常`
      };
    } catch (error) {
      this.logger.error('AI配置测试失败:', error);
      
      let errorMessage = '连接失败';
      if (error.message.includes('401')) {
        errorMessage = 'API密钥无效';
      } else if (error.message.includes('403')) {
        errorMessage = 'API密钥权限不足';
      } else if (error.message.includes('429')) {
        errorMessage = 'API调用频率超限';
      } else if (error.message.includes('ENOTFOUND')) {
        errorMessage = '无法连接到AI服务器,请检查网络和Base URL';
      } else if (error.message.includes('ECONNREFUSED')) {
        errorMessage = '连接被拒绝,请检查Base URL是否正确';
      } else if (error.message.includes('timeout')) {
        errorMessage = '请求超时,请检查网络连接';
      } else {
        errorMessage = error.message || '未知错误';
      }

      return {
        available: false,
        error: errorMessage,
        details: `测试失败原因: ${error.message}`
      };
    }
  }

  // 清理处理器
  removeAllHandlers() {
    ipcMain.removeAllListeners();
  }
}

module.exports = IPCHandlers;
```

## /src/helpers/logManager.js

```js path="/src/helpers/logManager.js" 
const fs = require('fs');
const path = require('path');
const os = require('os');

class LogManager {
  constructor() {
    this.logDir = this.getLogDirectory();
    this.logFile = path.join(this.logDir, 'app.log');
    this.funasrLogFile = path.join(this.logDir, 'funasr.log');
    this.ensureLogDirectory();
  }

  getLogDirectory() {
    // 在用户目录下创建日志文件夹
    const userDataPath = require('electron').app.getPath('userData');
    return path.join(userDataPath, 'logs');
  }

  ensureLogDirectory() {
    try {
      if (!fs.existsSync(this.logDir)) {
        fs.mkdirSync(this.logDir, { recursive: true });
      }
    } catch (error) {
      console.error('创建日志目录失败:', error);
    }
  }

  log(level, message, data = null) {
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      data,
      pid: process.pid
    };

    // 输出到控制台
    console[level](`[${timestamp}] ${message}`, data || '');

    // 写入日志文件
    try {
      const logLine = JSON.stringify(logEntry) + '\n';
      fs.appendFileSync(this.logFile, logLine);
    } catch (error) {
      console.error('写入日志文件失败:', error);
    }
  }

  info(message, data) {
    this.log('info', message, data);
  }

  error(message, data) {
    this.log('error', message, data);
  }

  warn(message, data) {
    this.log('warn', message, data);
  }

  debug(message, data) {
    this.log('debug', message, data);
  }

  // 记录FunASR相关日志
  logFunASR(level, message, data = null) {
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      data,
      source: 'FunASR'
    };

    console[level](`[FunASR] ${message}`, data || '');

    try {
      const logLine = JSON.stringify(logEntry) + '\n';
      fs.appendFileSync(this.funasrLogFile, logLine);
    } catch (error) {
      console.error('写入FunASR日志文件失败:', error);
    }
  }

  // 获取最近的日志
  getRecentLogs(lines = 100) {
    try {
      if (!fs.existsSync(this.logFile)) {
        return [];
      }

      const content = fs.readFileSync(this.logFile, 'utf8');
      const logLines = content.trim().split('\n').filter(line => line.trim());
      
      return logLines
        .slice(-lines)
        .map(line => {
          try {
            return JSON.parse(line);
          } catch {
            return { message: line, timestamp: new Date().toISOString() };
          }
        });
    } catch (error) {
      console.error('读取日志文件失败:', error);
      return [];
    }
  }

  // 获取FunASR日志
  getFunASRLogs(lines = 100) {
    try {
      if (!fs.existsSync(this.funasrLogFile)) {
        return [];
      }

      const content = fs.readFileSync(this.funasrLogFile, 'utf8');
      const logLines = content.trim().split('\n').filter(line => line.trim());
      
      return logLines
        .slice(-lines)
        .map(line => {
          try {
            return JSON.parse(line);
          } catch {
            return { message: line, timestamp: new Date().toISOString() };
          }
        });
    } catch (error) {
      console.error('读取FunASR日志文件失败:', error);
      return [];
    }
  }

  // 清理旧日志
  cleanOldLogs(daysToKeep = 7) {
    try {
      const cutoffTime = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
      
      [this.logFile, this.funasrLogFile].forEach(logFile => {
        if (fs.existsSync(logFile)) {
          const stats = fs.statSync(logFile);
          if (stats.mtime.getTime() < cutoffTime) {
            fs.unlinkSync(logFile);
            this.info(`清理旧日志文件: ${logFile}`);
          }
        }
      });
    } catch (error) {
      console.error('清理旧日志失败:', error);
    }
  }

  // 获取日志文件路径
  getLogFilePath() {
    return this.logFile;
  }

  getFunASRLogFilePath() {
    return this.funasrLogFile;
  }

  // 获取系统信息用于调试
  getSystemInfo() {
    return {
      platform: process.platform,
      arch: process.arch,
      nodeVersion: process.version,
      electronVersion: process.versions.electron,
      appVersion: require('electron').app.getVersion(),
      userDataPath: require('electron').app.getPath('userData'),
      logDir: this.logDir,
      env: {
        NODE_ENV: process.env.NODE_ENV,
        PATH: process.env.PATH,
        PYTHON_PATH: process.env.PYTHON_PATH
      }
    };
  }
}

module.exports = LogManager;
```

## /src/helpers/pythonInstaller.js

```js path="/src/helpers/pythonInstaller.js" 
const fs = require("fs");
const path = require("path");
const https = require("https");
const os = require("os");
const { runCommand, TIMEOUTS } = require("../utils/process");

class PythonInstaller {
  constructor(logger = null) {
    this.pythonVersion = "3.11.9"; // 与FunASR兼容的稳定版本
    this.logger = logger;
  }

  async downloadFile(url, outputPath, progressCallback = null) {
    return new Promise((resolve, reject) => {
      const file = fs.createWriteStream(outputPath);
      
      https.get(url, (response) => {
        if (response.statusCode !== 200) {
          reject(new Error(`下载失败: HTTP ${response.statusCode}`));
          return;
        }

        const totalSize = parseInt(response.headers['content-length'], 10);
        let downloadedSize = 0;

        response.on('data', (chunk) => {
          downloadedSize += chunk.length;
          if (progressCallback && totalSize) {
            progressCallback({
              downloaded: downloadedSize,
              total: totalSize,
              percentage: Math.round((downloadedSize / totalSize) * 100)
            });
          }
        });

        response.pipe(file);

        file.on('finish', () => {
          file.close();
          resolve();
        });

        file.on('error', (error) => {
          fs.unlink(outputPath, () => {}); // 错误时清理
          reject(error);
        });

      }).on('error', (error) => {
        reject(error);
      });
    });
  }

  async installPythonMacOS(progressCallback = null) {
    const platform = process.arch === 'arm64' ? 'macos11' : 'macosx10.9';
    const installerUrl = `https://www.python.org/ftp/python/${this.pythonVersion}/python-${this.pythonVersion}-${platform}.pkg`;
    
    const tempDir = os.tmpdir();
    const installerPath = path.join(tempDir, `python-${this.pythonVersion}.pkg`);

    try {
      // 首先尝试使用 Homebrew(推荐方式)
      try {
        await runCommand("brew", ["--version"], { timeout: TIMEOUTS.QUICK_CHECK });
        if (this.logger && this.logger.info) {
          this.logger.info("通过 Homebrew 安装 Python...");
        }
        
        if (progressCallback) {
          progressCallback({ stage: "通过 Homebrew 安装 Python...", percentage: 25 });
        }
        
        await runCommand("brew", ["install", "python@3.11"], { timeout: TIMEOUTS.INSTALL });
        
        if (progressCallback) {
          progressCallback({ stage: "Python 安装完成!", percentage: 100 });
        }
        
        return { success: true, method: "homebrew" };
        
      } catch (brewError) {
        if (this.logger && this.logger.info) {
          this.logger.info("Homebrew 不可用,使用官方安装包...");
        }
        
        if (progressCallback) {
          progressCallback({ stage: "下载 Python 安装包...", percentage: 10 });
        }
        
        // 下载官方 Python 安装包
        await this.downloadFile(installerUrl, installerPath, (progress) => {
          if (progressCallback) {
            progressCallback({ 
              stage: `下载 Python 安装包... ${progress.percentage}%`, 
              percentage: 10 + (progress.percentage * 0.4) // 10-50%
            });
          }
        });

        if (progressCallback) {
          progressCallback({ stage: "安装 Python...", percentage: 60 });
        }

        // 静默安装 Python
        await runCommand("sudo", ["installer", "-pkg", installerPath, "-target", "/"], { timeout: TIMEOUTS.INSTALL });
        
        // 清理
        fs.unlink(installerPath, () => {});
        
        if (progressCallback) {
          progressCallback({ stage: "Python 安装完成!", percentage: 100 });
        }
        
        return { success: true, method: "official_installer" };
      }
      
    } catch (error) {
      // 错误时清理
      if (fs.existsSync(installerPath)) {
        fs.unlink(installerPath, () => {});
      }
      throw error;
    }
  }

  async checkWindowsAdmin() {
    try {
      // 尝试读取受保护的注册表项来检查管理员权限
      await runCommand('reg', ['query', 'HKU\\S-1-5-19'], { timeout: TIMEOUTS.QUICK_CHECK });
      return true;
    } catch (error) {
      return false;
    }
  }

  async installPythonWindows(progressCallback = null) {
    const arch = process.arch === 'ia32' ? '' : '-amd64';
    const installerUrl = `https://www.python.org/ftp/python/${this.pythonVersion}/python-${this.pythonVersion}${arch}.exe`;
    
    const tempDir = os.tmpdir();
    const installerPath = path.join(tempDir, `python-${this.pythonVersion}.exe`);

    try {
      // 检查管理员权限
      const isAdmin = await this.checkWindowsAdmin();
      
      if (progressCallback) {
        progressCallback({ stage: "下载 Python 安装包...", percentage: 10 });
      }
      
      // 下载 Python 安装包
      await this.downloadFile(installerUrl, installerPath, (progress) => {
        if (progressCallback) {
          progressCallback({ 
            stage: `下载 Python 安装包... ${progress.percentage}%`, 
            percentage: 10 + (progress.percentage * 0.4) // 10-50%
          });
        }
      });

      if (progressCallback) {
        progressCallback({ stage: "安装 Python...", percentage: 60 });
      }

      // 根据管理员权限使用适当的安装选项
      const installArgs = isAdmin ? [
        "/quiet",
        "InstallAllUsers=1",
        "PrependPath=1",
        "Include_test=0",
        "Include_doc=0",
        "Include_dev=0",
        "Include_debug=0",
        "Include_launcher=1",
        "InstallLauncherAllUsers=1"
      ] : [
        "/quiet",
        "InstallAllUsers=0",
        "PrependPath=1",
        "Include_test=0",
        "Include_doc=0",
        "Include_dev=0",
        "Include_debug=0",
        "Include_launcher=1",
        "InstallLauncherAllUsers=0",
        "DefaultJustForMeTargetDir=%LOCALAPPDATA%\\Programs\\Python\\Python311"
      ];

      await runCommand(installerPath, installArgs, { timeout: TIMEOUTS.INSTALL });
      
      // 清理
      fs.unlink(installerPath, () => {});
      
      if (progressCallback) {
        progressCallback({ stage: "Python 安装完成!", percentage: 100 });
      }
      
      return { success: true, method: "official_installer" };
      
    } catch (error) {
      // 错误时清理
      if (fs.existsSync(installerPath)) {
        fs.unlink(installerPath, () => {});
      }
      throw error;
    }
  }

  async installPythonLinux(progressCallback = null) {
    try {
      if (progressCallback) {
        progressCallback({ stage: "检测 Linux 发行版...", percentage: 10 });
      }
      
      // 检测包管理器并安装 Python
      try {
        // 尝试 apt (Debian/Ubuntu)
        await runCommand("apt", ["--version"], { timeout: TIMEOUTS.QUICK_CHECK });
        
        if (progressCallback) {
          progressCallback({ stage: "通过 apt 安装 Python...", percentage: 30 });
        }
        
        await runCommand("sudo", ["apt", "update"], { timeout: TIMEOUTS.PIP_UPGRADE });
        await runCommand("sudo", ["apt", "install", "-y", "python3.11", "python3.11-pip", "python3.11-dev"], { timeout: TIMEOUTS.INSTALL });
        
        if (progressCallback) {
          progressCallback({ stage: "Python 安装完成!", percentage: 100 });
        }
        
        return { success: true, method: "apt" };
        
      } catch (aptError) {
        try {
          // 尝试 yum (RHEL/CentOS/Fedora)
          await runCommand("yum", ["--version"], { timeout: TIMEOUTS.QUICK_CHECK });
          
          if (progressCallback) {
            progressCallback({ stage: "通过 yum 安装 Python...", percentage: 30 });
          }
          
          await runCommand("sudo", ["yum", "install", "-y", "python311", "python311-pip", "python311-devel"], { timeout: TIMEOUTS.INSTALL });
          
          if (progressCallback) {
            progressCallback({ stage: "Python 安装完成!", percentage: 100 });
          }
          
          return { success: true, method: "yum" };
          
        } catch (yumError) {
          try {
            // 尝试 pacman (Arch Linux)
            await runCommand("pacman", ["--version"], { timeout: TIMEOUTS.QUICK_CHECK });
            
            if (progressCallback) {
              progressCallback({ stage: "通过 pacman 安装 Python...", percentage: 30 });
            }
            
            await runCommand("sudo", ["pacman", "-S", "--noconfirm", "python", "python-pip"], { timeout: TIMEOUTS.INSTALL });
            
            if (progressCallback) {
              progressCallback({ stage: "Python 安装完成!", percentage: 100 });
            }
            
            return { success: true, method: "pacman" };
            
          } catch (pacmanError) {
            throw new Error("未找到支持的包管理器 (apt, yum, 或 pacman)");
          }
        }
      }
      
    } catch (error) {
      throw error;
    }
  }

  async installPython(progressCallback = null) {
    const platform = process.platform;
    
    try {
      if (progressCallback) {
        progressCallback({ stage: "开始 Python 安装...", percentage: 5 });
      }
      
      switch (platform) {
        case 'darwin':
          return await this.installPythonMacOS(progressCallback);
        case 'win32':
          return await this.installPythonWindows(progressCallback);
        case 'linux':
          return await this.installPythonLinux(progressCallback);
        default:
          throw new Error(`不支持的平台: ${platform}`);
      }
      
    } catch (error) {
      throw error;
    }
  }

  async isPythonInstalled() {
    const possibleCommands = ['python3.11', 'python3', 'python'];
    
    // 在 macOS 上,还要检查常见的 Python 安装路径
    const additionalPaths = process.platform === 'darwin' ? [
      '/usr/local/bin/python3',
      '/usr/local/bin/python3.11',
      '/opt/homebrew/bin/python3',
      '/opt/homebrew/bin/python3.11',
      '/usr/bin/python3',
      '/Library/Frameworks/Python.framework/Versions/3.11/bin/python3',
      '/Library/Frameworks/Python.framework/Versions/3.10/bin/python3',
      '/Library/Frameworks/Python.framework/Versions/3.9/bin/python3',
    ] : [];
    
    // 首先检查 PATH 中的命令
    for (const cmd of possibleCommands) {
      try {
        const result = await runCommand(cmd, ['--version'], { timeout: TIMEOUTS.QUICK_CHECK });
        const versionMatch = result.output.match(/Python (\d+\.\d+)/);
        if (versionMatch) {
          const version = parseFloat(versionMatch[1]);
          // 接受任何 Python 3.x 版本
          if (version >= 3.0) {
            return { installed: true, command: cmd, version: version };
          }
        }
      } catch (error) {
        continue;
      }
    }
    
    // 然后检查 macOS 上的绝对路径
    for (const fullPath of additionalPaths) {
      try {
        if (fs.existsSync(fullPath)) {
          const result = await runCommand(fullPath, ['--version'], { timeout: TIMEOUTS.QUICK_CHECK });
          const versionMatch = result.output.match(/Python (\d+\.\d+)/);
          if (versionMatch) {
            const version = parseFloat(versionMatch[1]);
            if (version >= 3.0) {
              return { installed: true, command: fullPath, version: version };
            }
          }
        }
      } catch (error) {
        continue;
      }
    }
    
    return { installed: false };
  }
}

module.exports = PythonInstaller;
```

## /src/history.html

```html path="/src/history.html" 
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>转录历史 - 蛐蛐</title>
  <link rel="stylesheet" href="./index.css">
</head>
<body>
  <div id="history-root"></div>
  <script type="module" src="./history.jsx"></script>
</body>
</html>
```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!