# 开发构建
> 安装依赖、本地开发、构建打包、发布与仓库目录结构。产品功能说明见 [基础功能.md](./基础功能.md)、[语音朗读.md](./语音朗读.md)、[AI功能.md](./AI功能.md)、[书源找书.md](./书源找书.md)。
### 安装依赖
```bash
npm install
```
### 运行开发环境
```bash
npm run dev
```
仅打开找书窗口(等同传入 `--find-book`,默认进「书架」):
```bash
npm run dev:find
```
### 类型检查(可选)
```bash
npm run typecheck
```
### 预览构建结果(可选)
```bash
npm run preview
```
### 构建与打包
```bash
npm run build
```
打包产物默认输出到 `release` 目录,目标平台配置如下:
- macOS:`dmg`
- Windows:`nsis`、`portable`
- Linux:`AppImage`
自 2.3 起内置本地向量模型后安装包变大;`npm run build` / `npm run release` 的流程为:`electron-vite build` → **`electron-rebuild -f -w better-sqlite3,opencc`** → **`scripts/prune-pack-deps.mjs`**(裁剪将打入 **`app.asar` / `app.asar.unpacked`** 的 `node_modules`)→ `electron-builder`。**裁剪项清单**见下文 **项目结构 → `scripts/` →「打包前 node_modules 裁剪」**;**`opencc`** 运行时与打包要点见 [基础功能.md](./基础功能.md) → **「简繁与全半角转换」**。
相较于 2.2,`app.asar` 内 `node_modules` 仍会增大约 15~17MB(主要为当前平台的 `onnxruntime-node` JS + `@huggingface`;原生 `bin` 在 `app.asar.unpacked`,约 15MB)。`asarUnpack` 解包 `better-sqlite3`、`sqlite-vec*`、`onnxruntime-node/bin`、**`opencc`**、`@node-rs/jieba*` 等原生路径。
打包后本地开发异常可执行 **`npm ci`** 恢复依赖。
### 发布
#### GitHub Actions 自动发布(三端并行)
仓库已配置 [`.github/workflows/release.yml`](.github/workflows/release.yml)。推送版本 tag 后,会在 Windows / macOS / Linux 上并行构建并上传到同一 GitHub Release,无需在三台机器上分别打包。
```bash
# 更新版本号(会同步改 package.json、打 tag)
npm version patch|minor|major
# 推送代码与 tag(tag 推送会触发 CI)
git push && git push --tags
```
要求:**tag 须为 `v` + `package.json` 中的 `version`**(例如版本 `2.4.3` 对应 tag `v2.4.3`)。CI 会校验二者一致。
也可在 GitHub 仓库 **Actions → Release → Run workflow** 手动触发(用于补跑)。
CI 会并行构建以下架构(共 5 个 job),再由单独 job 统一发布到同一条 GitHub Release:
| 平台 | 架构 | Runner | 产物 |
| ---- | ---- | ------ | ---- |
| Windows | x64 | `windows-2025-vs2026` | NSIS 安装包 + Portable |
| macOS | arm64(Apple Silicon,M 系列) | `macos-latest` | DMG |
| macOS | x64(Intel) | `macos-15-intel`(Intel 原生;避免 arm64 交叉打入错误架构的 `opencc` / `better-sqlite3`) | DMG |
| Linux | arm64 | `ubuntu-24.04-arm` | AppImage |
| Linux | x64 | `ubuntu-latest` | AppImage |
macOS / Linux 多架构时,`artifactName` 含 `${arch}`,`publish.channel` 为 `latest-${arch}`,避免更新描述文件互相覆盖。Release 安装包文件名统一用 ASCII 的 `${name}`(`colortxt-…`),不用 `${productName}`(`彩读`):electron-builder 上传 GitHub Release 时不会对中文路径做 URL 编码,会导致 Windows/macOS 包上传失败。`build.beforePack` 会裁剪 `node_modules`;`build.onNodeModuleFile` 在 electron-builder 收集依赖时再次排除其他平台的原生包(解决 Linux x64 CI 包体膨胀)。各 build job 上传的安装包与 `latest*.yml`(自动更新元数据;不含 electron-builder 调试用的 `builder-debug.yml`)由 **Publish** job 汇总后执行 `electron-builder publish --policy always --files …` 发布;Actions 里 Windows 的 200MB+ artifact 是 Setup + Portable 两个 exe 的传输包,Release 页面上仍是两个独立文件。macOS 未配置签名证书时以未签名包发布(`CSC_IDENTITY_AUTO_DISCOVERY=false`)。
#### 本地手动发布
若需在单机上打包并上传,仍可使用本地命令,需 Personal access token(`repo` 权限)并设置 `GH_TOKEN`:
GitHub 用户 Settings -> Developer settings -> Personal access tokens,
生成一个 Token 并勾选 `repo` 权限。
设置 GitHub Token 环境变量:
```bash
# PowerShell
$env:GH_TOKEN = '你的TOKEN'
# 验证
echo $env:GH_TOKEN
# Windows CMD
set "GH_TOKEN=你的TOKEN"
# 验证
echo %GH_TOKEN%
# Bash / Zsh
export GH_TOKEN='你的TOKEN'
# 验证
echo $GH_TOKEN
```
```bash
# 创建一个新 tag
git tag v1.0.0
# 推送至远端
git push origin v1.0.0
# 构建打包并发布到 GitHub Releases
npm run release
```
> 本地 `npm run release` 只能打**当前机器**对应平台/架构的包,要多架构需走 CI 或在本机多次指定 `--platform` / `--arch` 分别打包。
### 撤销发布
发布后,如果想撤销发布,需要先在 [网页端](https://github.com/ssnangua/ColorTxt/releases) 删除相应的 Release 记录,然后再执行下面的命令删除 tag:
```bash
# 删除 tag
git tag -d v1.0.0
# 推送至远端删除
git push origin :refs/tags/v1.0.0
```
### 发布新版本
```bash
# 将改动提交到本地仓库
git commit -a -m "修改了xxx"
# 更新版本号
npm version patch|minor|major
# 将本地仓库的改动推送到远程仓库
git push
```
后面走发布流程。
### 项目结构
仓库根目录常用目录与文件:
| 目录 / 文件 | 说明 |
| ------------------------- | ---- |
| `src/` | 应用源码(主进程、预加载、渲染进程、共享常量) |
| `scripts/` | 构建与开发辅助脚本;**`postinstall`** 补丁(Monaco 查找栏 tooltip、transformers 内嵌 sharp);**`prune-pack-deps.mjs`** 由 `npm run build` / `release` 在 `electron-builder` 前调用(见 **「打包前 node_modules 裁剪」**),其余为本地调试/探测用 |
| `resources/` | 打包资源(应用图标、macOS entitlements 等) |
| `dist/` | `electron-vite build` 编译输出,供 `electron-builder` 打入安装包 |
| `release/` | `electron-builder` 最终产物输出目录 |
| `images/` | 文档用截图等(不参与应用打包逻辑) |
| `package.json` | npm 脚本与依赖;`electron-builder` 打包/发布相关配置也在此 |
| `vite.config.ts` | 供编辑器 / 工具链用的 Vite 占位配置;实际构建以 electron-vite 为准 |
| `electron.vite.config.ts` | electron-vite 主构建配置(三入口、`define` 注入、Monaco worker 输出、`index.html` 占位替换);细节见下节 |
##### `electron.vite.config.ts` 要点
- 主进程、preload、渲染进程三端入口与构建管线由 electron-vite 统一调度。
- 主进程 **`rollupOptions.external`** 含原生依赖 **`opencc`**(运行时 `createRequire` 加载,见 [基础功能.md](./基础功能.md) → **「简繁与全半角转换」**)。
- `define` 注入 `__APP_DISPLAY_NAME__` 与 `__GITHUB_REPO_URL__`:显示名优先取 `package.json` 的 `build.productName`,否则 `name`,再兜底 `ColorTxt`;仓库 URL 取 `homepage` 并去掉尾部 `/`。
- 渲染进程配合 `vite-plugin-monaco-editor`:`publicPath` 为 `monacoeditorwork`;`customDistPath` 仅基于 `outDir` 拼接 worker 输出目录,规避 Windows 下将 `root` 与 `outDir` 的绝对路径拼进 `path.join` 时的异常。
- `transformIndexHtml`:把 `index.html` 里的 `%APP_DISPLAY_NAME%` 替换为上述显示名。
- 主进程另打包 **`ai/rag/embedding/worker`** 入口(`@huggingface/transformers` 在 Worker 线程跑内置嵌入,见 [AI功能.md](./AI功能.md) → **「内置向量模型与缓存目录」**)。
##### `scripts/` 要点
| 文件 | 说明 |
| ---- | ---- |
| `prune-pack-deps.mjs` | **打包管线**:`electron-vite build` 之后、`electron-builder` 之前执行;支持 `--platform` / `--arch`。裁剪清单见下节 |
| `patch-nested-sharp-stub.mjs` | **`postinstall`**:将 `@huggingface/transformers` 内嵌 **`sharp`** 替换为 **`sharp-pack-stub`** |
| `patch-monaco-hover-pointer-below.mjs` | **`postinstall`**:恢复 Monaco 查找栏 tooltip 向下弹出(见 [基础功能.md](./基础功能.md) → **「Monaco 查找栏」**) |
| `sharp-pack-stub/` | 打包用 **`sharp` 占位包**(供 `@huggingface/transformers` 加载,非完整 native sharp) |
| `probe-chm.mjs` / `probe-chm.ts` | 命令行探测 CHM 解析(开发用,不参与打包) |
| `llm-extract-top-characters.mjs` | 本地大模型角色提取可行性测试(开发用,不参与打包) |
##### 打包前 node_modules 裁剪
由 **`scripts/prune-pack-deps.mjs`** 在打包前修改项目根目录的 `node_modules`(同时影响 **`app.asar`** 与 **`app.asar.unpacked`** 中的依赖树)。交叉编译时可传 `--platform win32|darwin|linux`、`--arch x64|arm64`(默认取当前机器)。裁剪后若需恢复完整依赖:**`npm ci`**。
交叉编译注意:`npm ci` 只会安装**构建机**架构的 optional 原生包。例如在 Apple Silicon 上若强行打 **macOS Intel** 包,默认没有 `@node-rs/jieba-darwin-x64` / `sqlite-vec-darwin-x64`;`prune-pack-deps` 会用 **`npm pack`** 补装并校验。但 **`opencc` / `better-sqlite3`** 依赖 **`electron-rebuild`**,无法可靠交叉编译——CI 的 macOS-x64 因此使用 **`macos-15-intel`**,并在 prune 前执行 **`electron-rebuild --arch <目标>`**;darwin 下再用 **`lipo`** 校验 `.node` 架构,防止再打出 arm64 二进制装进 Intel 包。
**整包移除(`node_modules` 顶层)**
| 包 / 模式 | 说明 |
| --------- | ---- |
| `onnxruntime-web` | Web/WASM 推理,内置向量不用 |
| `sharp`(完整包)、`@img/*` | 本应用不做图像推理;根目录 **`package.json` 依赖 `file:scripts/sharp-pack-stub`**,打包时覆盖完整 sharp,保证 asar 内可 `import "sharp"` |
| `protobufjs`、`@protobufjs/*`、`flatbuffers`、`long`、`platform`、`guid-typescript` | 删 `onnxruntime-web` 后的孤儿依赖 |
| `prebuild-install`、`napi-build-utils`、`node-abi`、`expand-template`、`mkdirp-classic`、`deep-extend`、`fs-constants`、`github-from-package`、`ini`、`rc`、`simple-concat`、`simple-get`、`tunnel-agent`、`strip-json-comments`、`tar-fs`、`tar-stream` | 仅 install / node-gyp 阶段使用 |
| `sqlite-vec-*-*`(非当前平台/架构) | 仅保留与 `--platform` / `--arch` 匹配的一个平台包 |
**按包裁剪的路径或文件**
| 包 | 移除内容 | 保留(运行时) |
| --- | -------- | -------------- |
| `@huggingface/transformers` | `dist/*` 除 `transformers.node.mjs`;`src/`、`types/`、README;`package.json` 中的 `onnxruntime-web`、`sharp` 依赖声明 | Node 入口 `transformers.node.mjs` |
| `@huggingface/jinja` | `src/`、`tsconfig.json`、README、`dist/*.d.ts.map` | `dist` 下编译产物 |
| `onnxruntime-node` | 非目标平台的 `bin/napi-v3/*`;`lib/`、`script/`、README;`dist/*.map`;Windows 下 **`DirectML.dll`**;Linux x64 下 **`libonnxruntime_providers_cuda.so`**、**`libonnxruntime_providers_tensorrt.so`**(内置向量固定 CPU;CI 另设 `ONNXRUNTIME_NODE_INSTALL_CUDA=skip` 跳过 postinstall 下载) | 当前平台 `bin/napi-v3/{plat}/{arch}` 与 `dist/*.js` |
| `onnxruntime-common` | `lib/`(TS 源码)、README、`dist/**/*.map`、`dist/**/*.d.ts` | `dist` 下 JS |
| `better-sqlite3` | **`deps/`**(含 **`sqlite3.c`**)、`src/`、`binding.gyp`、README;`package.json` 中的 **`prebuild-install`** 依赖声明 | `lib/`、`build/Release/*.node`、`bindings` |
| `font-list` | 非当前平台的 `libs/{darwin,linux,win32}`;`demo.js`、测试脚本、类型定义、README | `index.js`、`index.mjs`(ESM 入口)、`libs/core.js`、当前平台 `libs/` |
| `sqlite-vec` | README、`index.d.ts` | `index.cjs` / `index.mjs` |
| `sqlite-vec-{platform}-{arch}`(非目标平台/架构) | 整包移除;若目标包不存在则先 **`npm pack` 补装**再裁剪 | 目标平台原生扩展(如 `vec0.dylib` / `vec0.dll`) |
| `@node-rs/jieba` | README | 词云分词运行时入口 |
| `@node-rs/jieba-{platform}-{arch}`(非目标平台/架构) | 整包移除;若目标包不存在则先 **`npm pack` 补装**再裁剪 | 目标平台原生扩展(见 `pruneJiebaPlatformPackages`);打包时 **`asarUnpack`** 保留 jieba 原生 `.node` |
| `opencc` | **`deps/`**、**`src/`**、**`data/`**(源词典 txt)、**`scripts/`**、**`bin/`**、**`binding.gyp`**、**`prebuilds/`** 下各平台目录(npm prebuild 不兼容 Electron);**`build/`** 内除 **`Release/opencc.node`** 外的编译中间文件;**`node/cli.js`** | **`node/opencc.js`**、**`prebuilds/assets/`**(`.ocd2` 与 config)、**`build/Release/opencc.node`**(**`postinstall` 的 `electron-rebuild` 产物**,运行时优先加载);**`asarUnpack`** 解出原生与词典 |
**全 `node_modules` 树**
- 所有 **`*.map`**(source map)
- 各包下的 **`README.md`**、**`CHANGELOG.md`**
#### `src/` 总览
```text
src/
├── main/
│ ├── index.ts # 主进程入口:协议、窗口、IPC、单实例
│ ├── ipcHandlers.ts # 业务 IPC(对话框、目录、流式读、字体、主题等)
│ ├── registerTextConvertIpc.ts # `text-convert:opencc`(OpenCC 简繁)
│ ├── textConvertOpenCc.ts # OpenCC 主进程封装(createRequire、词典路径)
│ ├── detectTextEncoding.ts # 文本文件编码探测(BOM / jschardet / 中文 ANSI 启发式)
│ ├── registerAiIpc.ts # `ai:*` IPC 集中注册
│ ├── registerSecretsIpc.ts # 语音朗读方案密钥 IPC(`secrets:*`)
│ ├── secretStorage.ts # `userData/ai/secrets.v1.json` 加密读写(串行队列 + 原子落盘)
│ ├── ai/ # AI 相关主进程模块(按域分子目录,见下文「`src/main/ai/`」)
│ │ ├── infra/ # config、paths、dataFs、openAiCompatModelList(密钥经 secretStorage,不写 config 明文)
│ │ ├── shared/ # sleep 等跨域小工具
│ │ ├── chat/ # chat、agent、thinking、requestRetry、textFormatCleanup
│ │ ├── rag/ # vectorDb、segmentCache、jieba、embedding/、ragChapterDigest …
│ │ ├── txt2img/ # index、各 backend、shared、mergeZh、promptAdapt、testConnection
│ │ ├── voiceReadSpeaker.ts # 对白说话人/性别/情绪 AI 识别(`voiceRead:attributeSpeakers`)
│ │ ├── voiceReadSpeakerCache.ts # 按行缓存 AI 识别结果
│ │ └── tools/ # characterPortrait、mindmap、wordcloud*、characterPortraitFs
│ ├── voiceRead/ # TTS Provider 注册与 synthesis IPC(edge / dashscope / minimax / mimo)
│ │ ├── providerRegistry.ts
│ │ ├── registerVoiceReadIpc.ts
│ │ └── providers/ # edgeProvider、dashscopeProvider、minimaxProvider、mimoProvider
│ ├── voiceReadEdgeTts.ts # Edge TTS 合成(`voiceRead:edgeTts`)
│ ├── launchTxtHandlers.ts # 单实例与 `.txt` 启动/关联打开
│ ├── colortxtLocalProtocol.ts # `colortxt-local://` 本地资源短 URL
│ ├── windowFactory.ts # 创建 BrowserWindow、加载页、DevTools、边界钩子
│ ├── windowBounds.ts # 窗口几何持久化与屏幕校验
│ ├── globalShortcuts.ts # 系统级全局快捷键注册/注销
│ ├── updater.ts # 自动更新与相关 IPC
│ ├── updaterMessages.ts # 更新错误中文映射
│ ├── dialogInvoke.ts # 打开/保存对话框参数解析
│ └── messageBoxInvoke.ts # `showMessageBox` 参数解析
├── preload/
│ └── index.ts # `contextBridge` 暴露 `window.colorTxt`
├── renderer/
│ ├── index.html # 渲染进程 HTML 壳
│ └── src/
│ ├── main.ts # 挂载 Vue 应用
│ ├── App.vue # 根组件:布局、阅读器参数、侧栏与设置总线
│ ├── appShell.css # `App.vue` 作用域布局样式
│ ├── injectionKeys.ts # `provide` / `inject` 的 `InjectionKey`
│ ├── style.css # 全局样式、主题变量与 `.checkbox` 等控件基样式
│ ├── env.d.ts # 全局与 `window.colorTxt` 类型声明
│ ├── chapter.ts # 章节检测、行首缩进与物理/展示列映射
│ ├── icons.ts # 内联 SVG 图标汇总
│ ├── assets/ # 字体与静态图标
│ ├── public/
│ │ └── card-textures/ # 角色卡全息贴图(grain、glitter、cosmos 分层、foil 等)
│ ├── styles/
│ │ ├── characterCardHolo.css # 全息基础层、off/soft、透视与 popover 旋转
│ │ └── characterCardHoloEffects.css # 各 `data-char-texture` 效果样式
│ ├── components/ # Vue 组件(见下文组件表)
│ ├── composables/ # 根级组合式职责拆分(见补充说明)
│ │ ├── useConnectionTest.ts # 设置页「测试连接」按钮状态(pending/ok/fail)
│ │ ├── useAppBookmarkPins.ts # 书钉与书签(行号锚点、章节名、弹窗预览、Teleport 菜单等,见 [基础功能.md](./基础功能.md) → **「书签」**)
│ │ ├── useAppChapterListSync.ts # 列表「滚到当前」同步一拍
│ │ ├── useAppChapterNavigation.ts # 章节跳转与规则联动
│ │ ├── useAppFileSession.ts # 打开/目录/会话与流管道
│ │ ├── useAppFullscreenReaderLayout.ts # 全屏正文宽度与空白区交互
│ │ ├── useAppPersistence.ts # 设置、会话、列表、meta 持久化;语音密钥仅在设置确定/启动迁移时写保险库
│ │ ├── useAppReaderChrome.ts # 全屏顶/底/侧栏悬停与宽度
│ │ ├── useAppReaderUiPrefs.ts # 阅读偏好与 Monaco 同步
│ │ ├── useAppReadingProgress.ts # 阅读进度展示模型
│ │ ├── useAppSyncCurrentFileWatch.ts # 外部变更自动重载
│ │ ├── useAppShellThemeWatch.ts # 主题与原生主题 IPC
│ │ ├── useAppWindowBindings.ts # 快捷键、拖放、流结束与卸载落盘
│ │ ├── useReaderSidebarLists.ts # 侧栏虚拟列表与筛选排序
│ │ ├── useReaderInlineSearch.ts # 阅读区内联搜索
│ │ ├── useReaderAnnotations.ts # 选区标注/笔记工具条、装饰索引、笔记面板
│ │ ├── useFileListCategorySort.ts # 分类下拉与排序文案
│ │ ├── useFileListSelection.ts # 文件列表编辑模式多选
│ │ ├── useFileListMenus.ts # 右键与分类浮层
│ │ ├── useTxtStreamPipeline.ts # 大文件流式解析与映射
│ │ ├── useAiChapterPlainTextBridge.ts # 响应 `ai:chapter-plain-request` 回传章文
│ │ ├── useAiSmartFormat.ts # 见 AI功能.md「AI 智能排版」
│ │ ├── useReaderSmartFormatDiff.ts # 见 AI功能.md「AI 智能排版」
│ │ ├── useAiFoldContentSelectAll.ts # 助手折叠区全选
│ │ ├── useSecretStorageHint.ts # 设置页 API 密钥落盘说明文案
│ │ ├── useCharacterCardTilt.ts # 角色卡指针倾斜 + 光泽 CSS 变量(弹簧跟手/回正)
│ │ ├── useCharacterCardPopoverZoom.ts # 角色卡原位放大(Teleport、平移/缩放/Y 旋转)
│ │ ├── useCharacterRosterReorder.ts # 角色卡网格 Sortable 拖动排序(飞回动画、翻面过滤)
│ │ ├── useSortableReorder.ts # 通用列表 Sortable(`.sortableRowHandle` 手柄)
│ │ ├── useAppVoiceRead.ts # 语音朗读主循环、行跳转、合成状态、侧栏跳转拦截
│ │ ├── useAppTimedScroll.ts # 定时滚动开关、间隔 tick、与朗读/编辑/到底互斥
│ │ ├── useAppHeaderLayout.ts # 顶栏响应式断点(字体组/格式组是否收入「更多」)
│ │ └── useVoiceReadProfileDraft.ts # 设置页朗读方案草稿(`SettingsVoiceReadPanel`)
│ ├── constants/
│ │ ├── appUi.ts # UI 常量、存储 key、侧栏与字号边界
│ │ ├── readerPalette.ts # 阅读器表面色、token 独立配色开关与有效色解析
│ │ ├── highlightColors.ts # 自定义高亮色默认与解析
│ │ ├── lineationColors.ts # 划线标注色默认与解析
│ │ ├── annotationColors.ts # 划线色下标、上次选色偏好
│ │ ├── fileCategories.ts # 文件分类与排序常量
│ │ ├── readerSidebarTab.ts # 侧栏 tab id 常量
│ │ ├── voiceRead.ts # 朗读设置类型、默认项与 merge 工具
│ │ ├── voiceReadEdgeTts.ts / voiceReadMinimax.ts # 引擎回退音色常量
│ │ ├── wordcloudUi.ts # 词云角度布局模式
│ │ ├── wordcloudPalettes.ts # 词云配色预设
│ │ ├── timedScroll.ts # 定时滚动范围/间隔默认值与 merge
│ │ └── appHeaderLayout.ts # 顶栏紧凑布局断点(1030 / 830 px)
│ ├── monaco/ # Monaco 阅读器扩展
│ │ ├── chapterStickyScroll.ts # 黏性章节大纲
│ │ ├── readerEditorOptions.ts # 编辑器选项构建
│ │ ├── readerDiffEditorOptions.ts # 见 AI功能.md「AI 智能排版」
│ │ ├── readerInlineDecorations.ts # 章节行内装饰与 Monarch
│ │ ├── readerImageViewZones.ts # 插图 ViewZone
│ │ ├── readerKeyScroll.ts # 键盘滚动
│ │ ├── txtrHighlightMonarch.ts # 自定义高亮词 Monarch 规则
│ │ └── txtrTextMonarch.ts # `txtr-text` Monarch 语言
│ ├── reader/
│ │ ├── readerDisplayPipeline.ts # 物理行 → 展示正文(压缩/缩进/章节留白)
│ │ ├── readerTextFormat.ts # 编辑态格式化(压缩空行、行首缩进)封装
│ │ ├── initialSidebarTab.ts # 首屏侧栏 tab(是否将加载文件)
│ │ ├── chapterIndex.ts # 视口章节下标(二分)
│ │ ├── lineMapping.ts # 物理行与显示行映射
│ │ ├── readerViewportAnchor.ts # 视口锚点与程序性滚动字高带(编辑切换 / 章节·书签跳转)
│ │ ├── ebookAnchorLookup.ts # 电子书内链行映射
│ │ ├── readerEbookPointer.ts # 内链点击命中辅助
│ │ ├── readerHighlightGeometry.ts # 高亮词/标注浮动层几何
│ │ └── readerAnnotationDecor.ts # 标注视口 inline 装饰与动态 CSS 规则
│ ├── markdown/ # Markdown 章节、内链与图片
│ │ ├── markdownChapter.ts # ATX 标题、章节表
│ │ ├── markdownBlockContext.ts # 围栏/缩进代码块(# 误识别防护)
│ │ ├── markdownLinkShared.ts # marked 内/外链扫描、sidecar 类型(转换/阅读器共用)
│ │ ├── markdownInternalLinks.ts # 内链剥离、sidecar 安装与 Monaco 装饰
│ │ └── markdownImages.ts # 块级 `` 扫描与资源路径解析
│ ├── ebook/ # 电子书转 Markdown
│ │ ├── ebookFormat.ts # 路径判定与输出基名
│ │ ├── ebookTitleMatch.ts # 目录标题匹配用纯文本提取
│ │ ├── pathUtils.ts # 路径片段规范化
│ │ ├── yieldToUi.ts # 长任务让出主线程
│ │ └── convert/ # 格式解析、注入与写出
│ │ ├── convertEbookToMarkdown.ts # 调度、缓存与写出
│ │ ├── ebookTypes.ts # 转换产物类型
│ │ ├── ebookTocAnchorInjection.ts # 嵌入目录 → ATX / toc span 注入
│ │ ├── ebookSpineLineMatch.ts # spine 节内标题行匹配与行变更
│ │ ├── ebookTocTypes.ts # EmbeddedTocEntry、目录去重
│ │ ├── ebookEpubNav.ts # EPUB nav/NCX 目录解析
│ │ ├── ebookMarkdownEmit.ts # span / MD 内链 / ATX 前缀
│ │ ├── ebookFootnoteLinkFragments.ts # 脚注回跳 fragment
│ │ ├── ebookStemOnlyMdLinks.ts # 无文案 stem 内链
│ │ ├── ebookLinkIconHeuristics.ts # 链接图标 vs 块级图判定
│ │ ├── parseEpub.ts # EPUB 解析
│ │ ├── parseMobi.ts # MOBI / AZW3
│ │ ├── parsePdf.ts # PDF 文本层 + 书签大纲
│ │ ├── parseFb2.ts # FB2 / FBZ
│ │ ├── parseChm.ts # CHM 解析入口
│ │ ├── chm/
│ │ │ ├── chmArchive.ts # CHM 归档读取
│ │ │ └── lzxDecode.ts # LZX 解压
│ │ └── mobi/
│ │ ├── foliateMobi.js # Foliate MOBI 引擎
│ │ └── foliateMobi.d.ts # 类型声明
│ ├── ai/ # 建索引与内置嵌入就绪校验
│ │ ├── buildBookVectorIndex.ts # 按章节切块并写入向量库
│ │ └── embeddingReady.ts # 建索引前检查内置模型是否已下载
│ ├── aiSmartFormat/ # 见 AI功能.md「AI 智能排版」
│ │ ├── aiSmartFormatSegments.ts
│ │ ├── aiSmartFormatTextPostProcess.ts
│ │ ├── aiSmartFormatReviewTypes.ts
│ │ └── smartFormatDiffRevertUi.ts # Diff 预览放弃确认文案
│ ├── aiAssistant/ # AI 助手数据与导出
│ │ ├── aiAssistantTypes.ts # UI 消息等类型
│ │ ├── aiAssistantSegments.ts # 消息分段
│ │ ├── aiAssistantPlainText.ts # 可复制纯文本
│ │ ├── aiAssistantDbMessages.ts # DB 行与 UI 互转
│ │ ├── aiAssistantHistoryFormat.ts # 历史快照格式
│ │ ├── aiAssistantExport.ts # 对话导出
│ │ ├── parseMindmapToolResult.ts # mindmap 工具 JSON → UI 附件
│ │ └── parseWordcloudToolResult.ts # wordcloud 工具 JSON → UI 附件
│ ├── directives/
│ │ └── aiStickScroll.ts # 助手折叠区粘底
│ ├── services/
│ │ ├── appDialog.ts # 应用内对话框队列
│ │ ├── appToast.ts # Toast 服务
│ │ ├── fileListService.ts # 目录与文件列表合并
│ │ ├── fileOpenService.ts # 打开前校验与恢复行号
│ │ ├── physicalLineStream.ts # 流式按行切分
│ │ ├── shortcutRegistry.ts # 快捷键动作注册表
│ │ ├── shortcutUtils.ts # 快捷键规范化与冲突
│ │ ├── shortcutService.ts # 窗口级快捷键监听
│ │ ├── textConvertApply.ts # 展示层/编辑态转换编排(OpenCC IPC + 全半角)
│ │ └── voiceRead/ # 朗读分段、合成客户端、排播、音色解析、预览与缓存
│ ├── stores/
│ │ ├── cacheStore.ts # localStorage 设置解析
│ │ ├── fileMetaStore.ts # 单文件 meta
│ │ └── recentHistoryStore.ts # 最近打开 MRU
│ ├── utils/
│ │ ├── color.ts # 颜色换算
│ │ ├── format.ts # 字数与大小格式化
│ │ ├── fontFamilyCss.ts # `font-family` 片段
│ │ ├── presetFontDefinitions.ts # 预设字体映射
│ │ ├── dragDropFsPaths.ts # 拖放路径解析
│ │ ├── fileListPanelDisplay.ts # 文件行展示逻辑
│ │ ├── modalStack.ts # 弹窗层叠与 ESC
│ │ ├── defaultCacheDirs.ts # 默认缓存目录解析
│ │ ├── fullscreenHeaderFloat.ts # 全屏顶栏浮层命中
│ │ ├── fullscreenSidebarFloat.ts # 全屏侧栏浮层命中
│ │ ├── aiBookHash.ts # 书籍哈希(渲染侧)
│ │ ├── aiChunkBook.ts # 按 token 切块
│ │ ├── currentChapterPlainText.ts # 按章索引从阅读器切片(与侧栏字数一致)
│ │ ├── readerSurroundingPlainText.ts # 视口附近节选
│ │ ├── aiMarkdownMarkedSetup.ts # marked + KaTeX 配置
│ │ ├── aiMarkdownMarkedPrep.ts # Markdown 预处理
│ │ ├── aiMarkdownChapterRef.ts # 章节引用 token 链接化
│ │ ├── aiToolFoldBody.ts # 工具折叠区 DOM 辅助
│ │ ├── readerAnnotations.ts # 标注范围、列表行、章节分组、normalize
│ │ ├── readerAnnotationExport.ts # 标注 JSON/Markdown 导出与导入
│ │ ├── characterCardTiltDom.ts # 角色卡拖动排序 DOM(放大/飞回动画、倾斜回正)
│ │ ├── characterCardSpring.ts # 角色卡倾斜弹簧参数(跟手 / 回正)
│ │ ├── appShellMenuPosition.ts # 浮动菜单锚点定位(`below-center` 等;`FontPicker` / 侧栏 flyout)
│ │ ├── voiceReadVoiceGroups.ts # 各引擎音色下拉分组(Edge / 通义 / MiniMax / MiMo 等)
│ │ └── defaultCacheDirs.ts # 默认 AI 数据/模型/立绘缓存目录(与 preload 对齐)
└── shared/
├── packageDerived.ts # 从 package 派生的共享元数据
├── voiceReadEngines.ts # 引擎注册表(edge / system / dashscope / minimax / mimo)
├── voiceReadProfiles.ts # 朗读方案、单/多音色设置;磁盘剥密钥(`stripVoiceReadSettingsApiKeysForDisk`)
├── voiceReadEngineConfig.ts # 各引擎 API 密钥与模型字段(通义 / MiniMax / MiMo 分开)
├── voiceReadSynthesis.ts # 合成请求/结果与音色选项类型
├── voiceReadSynthesisIpc.ts # `voiceRead:synthesize` / `listVoices` / `healthCheck`
├── voiceReadSpeakerIpc.ts # `voiceRead:attributeSpeakers` 载荷
├── voiceReadEmotion.ts # 情绪参数(通义 instruct / MiMo 自然语言;MiniMax 枚举)
├── voiceReadEdgeTtsVoices.ts / voiceReadDashscopeVoices.ts / voiceReadMinimaxModels.ts / voiceReadMimoModels.ts / voiceReadMimoVoices.ts # 音色与模型预设
├── chatModelPresets.ts # MiMo 等对话模型列表排序/过滤(`sortChatModelsForBaseUrl`)
├── ebookExtensions.ts # 电子书扩展名常量
├── ebookConvertPaths.ts # 默认转换输出子目录名
├── aiTypes.ts # AI 共享类型与默认配置(含 `embedding.provider`、`aiDataCacheDir`)
├── aiDataPaths.ts # 默认 `userData/ai/data`、`userData/ai/model-cache` 路径拼接
├── builtinEmbeddingModels.ts # 内置嵌入模型目录(BGE / E5)、HF 镜像默认值
├── builtinEmbeddingIpc.ts # 内置嵌入 IPC 载荷(模型 id + 配置快照)
├── apiEndpointPresets.ts # 对话/文生图服务商预设(含 MiniMax、小米 MiMo、Agnes AI 等);`applyOpenAiCompatAuthHeaders`
├── aiEndpointProfiles.ts # 对话/文生图多套配置方案(chatProfiles / txt2imgProfiles);profile 密钥映射与孤儿回收
├── secretSlots.ts # 密钥保险库 slot 名(`@shared/secretSlots`)
├── aiSystemPromptPresets.ts # 附加系统提示词内置预设(虚构文学分析等)
├── aiTokenUsage.ts # usage 解析、缓存命中、花费估算与展示文案
├── aiTxt2ImgIpc.ts # 文生图 IPC 载荷类型
├── txt2ImgBackend.ts # 文生图 backend、prompt 族、尺寸解析
├── txt2ImgCloudSizePresets.ts # 云端固定尺寸档与默认对齐(512×768 参考)
├── txt2ImgCloudModelPresets.ts # 各云端模型建议列表与万相 API 版本判定
├── txt2ImgOpenAiQuality.ts # OpenAI 图像画质选项
├── aiSkills.ts # 技能元数据与合并工具
├── aiSmartFormatTypes.ts # 见 AI功能.md「AI 智能排版」
├── aiAgentSkillToolNames.ts # Agent 技能名常量
├── aiChapterRefPrompt.ts # 章节引用提示词约定
├── aiMindmapIntent.ts # 用户原话驱动的导图意图(explicit/auto/none)与 rag 后追问
├── aiWordcloudIntent.ts # 词云意图检测、mode 判定与 semanticQuery 提炼
├── aiWordcloudSemanticFocus.ts # 语义词云:LLM 抽取 + 按 semanticQuery 筛选 prompt
├── aiWordcloudStopwords.ts # 词云停用词表
├── aiVisualToolIntent.ts # 词云与思维导图同轮注入(互斥 / 双工具)
├── characterTypes.ts # 角色侧栏类型
├── characterAliases.ts # 角色别名解析、合并与展示(检索/立绘共用)
├── characterPortraitPaths.ts # 立绘路径与文件名约定
├── chapterMatchBuiltinPatterns.ts # 内置章节正则
├── textConvertTypes.ts # 顶栏「转换」菜单项、模式类型与 OpenCC 映射
├── textWidthConvert.ts # 字母/数字全半角互转
├── colorTxtOpenSaveDialog.ts # 打开/保存对话框类型
└── colorTxtShowMessageBox.ts # MessageBox 类型
```
#### `src/` 目录树各文件补充说明
下文对应「`src/` 总览」目录树中各 `#` 注释的展开;与后文 **`src/main/`**、**`ipcHandlers`**、**`preload`** 等专节交叉时,以专节中的流程与边界说明为准。**AI、向量、文生图、角色侧栏** 等模块的宏观说明、Vue 组件表与 `userData` 路径另见 [AI 阅读助手与相关能力](./AI功能.md);**语音朗读** 另见 [语音朗读](./语音朗读.md)。
##### `src/main/`(与专节交叉索引)
**`index.ts`**、**`ipcHandlers.ts`**、**`detectTextEncoding.ts`**、**`globalShortcuts.ts`**、**`launchTxtHandlers.ts`**、**`windowFactory.ts`**、**`windowBounds.ts`**、**`updater.ts`**、**`updaterMessages.ts`**:生命周期、IPC 清单、流式读与 Monaco 写入、单实例与窗口行为等,见下文 **`src/main/`** 各小节。
##### `src/main/`(其余模块)
- **`registerAiIpc.ts`**:AI / RAG / Agent / 文生图 / 角色立绘等 `ai:*` IPC 集中注册(实现分散在 **`ai/`** 子目录;智能排版见 [AI功能.md](./AI功能.md))。
- **`colortxtLocalProtocol.ts`**:`colortxt-local://resource/{uuid}` 短 URL 本地协议;磁盘路径注册后供 `
` / 阅读器插图安全访问。
- **`detectTextEncoding.ts`**:文本文件编码探测,供 `ipcHandlers` 的 `file:stream` 与 `file:readWholeTextFile` 共用;详见下文 **`detectTextEncoding.ts`** 专节。
- **`dialogInvoke.ts`** / **`messageBoxInvoke.ts`**:系统对话框参数解析(与 `@shared/colorTxtOpenSaveDialog`、`@shared/colorTxtShowMessageBox` 对齐)。
##### `src/main/ai/`(AI 模块)
- **`infra/`**:**`paths.ts`**(数据/模型缓存根、向量库与 segment 库路径)、**`dataFs.ts`**(缓存目录迁移与旧版布局升级)、**`config.ts`**(`config.json` 读写;API 密钥经 **`secretStorage`** 写入 **`secrets.v1.json`** 的 `*ProfileKeys`,config 不含明文 Key)、**`openAiCompatModelList.ts`**(`GET /models` 拉取模型 id;认证经 **`applyOpenAiCompatAuthHeaders`**,对话与文生图测试连接共用)。
- **`shared/`**:**`sleep.ts`**(可中断 sleep;`chat/requestRetry` 与文生图轮询共用)。
- **`chat/`**:**`chat.ts`**(OpenAI 兼容流式对话)、**`chatThinking.ts`**(深度思考参数与流式推理 delta)、**`agentChat.ts`** / **`agentTools.ts`**(Agent 工具循环与 `ai:agent:event`)、**`requestRetry.ts`**(可重试 AI 请求)、**`textFormatCleanup.ts`**(`ai:text-format:*`,见 [AI功能.md](./AI功能.md) → **「AI 智能排版」**)。
- **`rag/`**:**`vectorDb.ts`**(SQLite + sqlite-vec)、**`embedding/index.ts`**(远程 `/embeddings` 与内置分支)、**`embedding/localBackend.ts`** + **`embedding/worker.ts`**(Transformers.js Worker;打包入口 **`ai/rag/embedding/worker`**)、**`segmentCache.ts`** / **`jieba.ts`**(词云分词缓存)、**`bookHash.ts`**、**`ragChapterDigest.ts`**(超长章压缩提要)、**`chapterPlainTextBridge.ts`**(向渲染进程索取章文)、**`resolveSqliteVecPath.ts`**(sqlite-vec 原生扩展路径)。
- **`txt2img/`**:**`index.ts`**(A1111 / Comfy / 云端路由)、各 **`a1111` / `comfy` / `dashScope` / `openAI` / `agnes` / `minimax` / `stability`** 后端、**`shared.ts`**、**`mergeZh.ts`**(通用+角色中文 prompt 顿号拼接)、**`promptAdapt.ts`**、**`testConnection.ts`**。
- **`voiceReadSpeaker.ts`**:旁白/对白多音色模式下,按**行**调用对话模型识别引号对白说话人、性别与情绪(**`voiceRead:attributeSpeakers`** IPC;仅传入**当前行原文**与角色表姓名/别名,**不**检索全书上下文;结果按行缓存于 **`voiceReadSpeakerCache.ts`**)。依赖 **AI 阅读助手** 已启用且配置对话端点;可选 **`includeEmotion`**(需 **`emotionEnabled`** 且引擎支持情绪)。
- **`tools/`**:**`characterPortrait.ts`**(角色检索/画风/立绘编排;**别名**发现与 RAG 查询扩展)、**`mindmapTool.ts`**、**`wordcloudTool.ts`** / **`wordcloudChapterFetch.ts`**、**`characterPortraitFs.ts`**(立绘缓存目录迁移与图片复制)。
**`agentChat.ts`** 补充:向渲染进程推送 `ai:agent:event`(含 `reasoning_delta`、`token_usage_*`、`tool_progress` 等);`ragContext` 经 **`chapterPlainTextBridge`** 取章文,超长章由 **`ragChapterDigest`** 压缩;「生成章节匹配规则」专轮见 **`@shared/chapterMatchAgentTurn`**(禁止 `ragContext`)。
##### `src/preload/index.ts`
预加载通过 `contextBridge` 向渲染进程暴露受控 API 的完整清单与语义,见下文 **`src/preload/index.ts`(预加载)**。
##### `src/renderer/src/`
###### `App.vue`
根组件:负责**布局与全局状态串联**;书钉/书签、全屏阅读区布局、阅读进度等拆到各 composables。
- **阅读器入参**:向 `ReaderMain` 传入阅读偏好与当前主题的 **`highlightColorsLight` / `highlightColorsDark`**(合并默认后)、**`lineationColors`**(当前 shell 主题对应的标注色表)、**`lineationLastColors`**、本书 **`readerAnnotations`**、**`monacoCustomHighlight`**、**`txtrDelimitedMatchCrossLine`**(与内容上色配合的成对符号跨行匹配)、**`stickyChapterTitleEnabled`**(黏性章节条,见 **「黏性章节条」**)、**`monacoSmoothScrolling`**、合并后的 **`highlightWordsByIndex`**(global + 本书)及仅本书的 **`highlightWordsByIndexBookOnly`**(选区浮层判定用)。标注 upsert/remove 经 **`fileMetaStore`** 写回 **`colorTxt.file.meta`**;阅读器选区 **「问 AI」** 经 **`onAskAiWithQuote`** 切到侧栏 AI 助手并 **`prefillQuotedText`**。
- **快捷键与配色**:维护 `shortcutBindings` 并传给 `AppHeader`;**`openColorScheme`** 打开配色弹窗。
- **侧栏文件列表**:**分类筛选**、**排序模式**、**分类目录**(`fileCategory` / `fileSort` / `fileCategoryCatalog`)与 `FileListPanel`、`useAppPersistence` 联动。
- **AI 与立绘**:**AI 技能**(`aiSkillsEnabled` / `aiSkillOverrides` / `aiCustomSkills`)、侧栏 **「深度思考」** / **「防剧透」**(`aiAssistantDeepThinking` / `aiAssistantSpoilerSafe`,经 `ReaderSidebar` 绑定 `AiAssistantPanel` 与角色检索抽屉)、**角色立绘缓存目录**(`characterPortraitCacheDir`)、词云 UI 偏好(**`wordcloudFontFamily`** / **`wordcloudAngleMode`** / **`wordcloudPaletteId`**,经 `AiWordcloudView` 与 `useAppPersistence` 持久化)等与设置/迁移联动;**`useAiChapterPlainTextBridge`**(`App.vue` 注册)响应主进程 `ragContext` 的章节原文索取。
- **AI 智能排版**:见 [AI功能.md](./AI功能.md) → **「AI 智能排版」**。
- **设置弹窗**:由 **`SettingsPanel.vue`** 组织 **`SettingsTabBar`** 与子面板 **`SettingsGeneralPanel`** / **`SettingsReadingPanel`** / **`SettingsEditPanel`** / **`SettingsAIPanel`** / **`SettingsVectorModelPanel`** / **`SettingsTxt2ImgPanel`**(页签文案「角色卡」,文生图与角色卡出图配置)/ **`SettingsSkillsPanel`** / **`SettingsVoiceReadPanel`**(**语音朗读**);技能编辑用 **`SettingsSkillEditModal.vue`**(见下文组件表)。
- **语音朗读**:**`useAppVoiceRead`** 驱动顶栏 **`VoiceReadToolbar`**;播放中 **`isVoiceReadNavigationBlocked`** 拦截侧栏 tab 切换与查找栏打开;朗读中 **`shortcutService`** 仅吞掉滚动/章节/查找相关快捷键(字号等仍可用);设置 **`voiceRead.*`** 持久化于 **`colorTxt.ui.settings`**(见 [语音朗读](./语音朗读.md));与 **定时滚动** 互斥(见 **「定时滚动」**)。
- **定时滚动**:**`useAppTimedScroll`** 驱动顶栏 **定时滚动** 开关;按 **`timedScroll`** 设置间隔调用 **`ReaderMain.scrollByPageStep` / `scrollByLineStep`**;设置 **`timedScroll`** 持久化于 **`colorTxt.ui.settings`**(见 **「定时滚动」**)。
- **全屏与浮层**:全屏时 **`fullscreenFileListPopoversOpen` / `fullscreenAiAssistantPopoversOpen`** 交给 `useAppReaderChrome`,避免 Teleport 浮层打开时误收起全屏侧栏。
- **根级挂载**:`AppOverlays`、`AppDialogHost`、`AppToastHost` 等。
###### 其它入口与样式
- **`appShell.css`**:根组件专用样式(由 `App.vue` 以 scoped 方式引入):全屏顶/底/侧栏布局、正文区等。
- **`injectionKeys.ts`**:`provide` / `inject` 用的 `InjectionKey`(如书签备注输入框 `ref`,供 `useAppBookmarkPins` 与 `AppOverlays` 对齐)。
- **`chapter.ts`**:章节标题检测、章节匹配规则(正则)的存取与校验;**`physicalOffsetToDisplayOffset` / `displayOffsetToPhysicalOffset`** 与 **`physicalRangeToDisplayColumns` / `displayColumnsToPhysicalSlice`**(行首全角缩进下的物理列 ↔ Monaco 展示列,章节标题行可 **`exemptChapterTitle`**;供标注、侧栏搜索跳转);内置三条 pattern 与 `@shared/chapterMatchBuiltinPatterns` 同源。
- **`icons.ts`**:各功能图标的 SVG 字符串汇总,供组件内联使用。
###### `composables/`
- **`useAppBookmarkPins.ts`**:书钉与书签:列表项、视口内活动书签、添加/移除/跳转及书签弹窗交互;**`readerEditMode`** 下书签跳转与视口判定按物理行 = Monaco 行(不经滤空映射)。**正文预览**(列表与弹窗)**只读**读展示层 **`getDisplayLineContent`**,**编辑态**读物理行。**章节名**(侧栏列表与添加/编辑弹窗预览)用当前 **`chapters`** 与 **`reader/chapterIndex`** 的 `pickActiveChapterIdx` 推断;**持久化行号**、**锚点行**、**弹窗预览**、**右键菜单 Teleport** 等见下文 **「书签(行号语义、侧栏与弹窗)」**。
- **`useAppChapterListSync.ts`**:侧栏章节/文件列表「滚到当前」的一拍状态(与 VirtualList 配合)。
- **`useAppChapterNavigation.ts`**:章节跳转、章节规则与最近文件、侧栏标签等联动;**`jumpToChapter`** 经 **`ReaderMain.scrollToLineNearTop`**,锚点字高带为 **`chapterJumpAnchorSlotFromTop(headingLevel)`**(与黏性章节条层数对齐,见 **「黏性章节条」**);只读展示正文变更后由 **`buildChaptersFromReaderDisplayText`** 重算章节;应用章节规则后重载当前文件时以视口末行恢复阅读位置(与 `useAppReaderUiPrefs` 切换排版一致)。
- **`useAppFileSession.ts`**:打开文件/选目录、会话快照恢复、与流管道和持久化衔接;`resetSession` 置 `readingProgressSynced` 为 `false`;导入目录合并列表时若当前分类筛选为具体分类名,会把新项写上对应 `category`(「全部 / 未分类」筛选下不写)。
- **`useAppFullscreenReaderLayout.ts`**:全屏时正文区域宽度样式;layout 上点击左右空白聚焦编辑器;两侧空白区 `wheel` 转交 `ReaderMain.delegateEditorWheelFromBrowserEvent`(见下文「全屏正文宽度与两侧空白滚轮」);事件来自侧栏子树时不劫持(含 Shadow DOM 向上判定)。
- **`useAppPersistence.ts`**:界面设置、会话快照、最近打开列表、文件元数据(书签等)的加载与保存;`persistFileMeta` 受 `readingProgressSynced` 门控;`persistWindowUnloadState` 在「清除缓存」后的刷新流程中可被 `skipUnloadPersistenceSessionKey` 跳过(见「清除缓存(设置面板)」)。
- **`useAppReaderChrome.ts`**:全屏阅读时顶栏/底栏/侧栏悬停显隐与侧栏宽度拖拽。
- `fullscreenSidebarPopoversSuppressCollapse`:文件列表 / AI 助手 Teleport 菜单打开时抑制侧栏误收起。
- 内部用 `utils/fullscreenHeaderFloat` / `fullscreenSidebarFloat` 判断指针是否在全屏顶栏或侧栏浮层子树内。
- **`useAppReaderUiPrefs.ts`**:字号/行高/字体、高级换行与内容着色等阅读偏好与 Monaco、持久化同步。
- **只读**下切换压缩空行/行首缩进/**转换**(简繁、字母、数字):不再整文件 `openFilePath` 重载,而是 **`stream.applyReaderDisplayFromPhysicalLines`** 基于内存中的物理行重算展示正文并恢复视口(`syncChaptersAfterViewportSettled`);失败则回滚开关。
- 字号增大时按字号上限夹行高倍数。
- **`useAppReadingProgress.ts`**:阅读进度展示模型:以视觉滚动进度为主(到底=100%),并输出 `(当前行/总行)` 文案;供底栏/侧栏/最近打开统一使用。底栏**总字数**来自 **`totalCharCount`**(展示正文 `text.length`;编辑态由 **`resyncMirrorFromReader`** 与 Monaco 同步)。
- **`useAppSyncCurrentFileWatch.ts`**:「同步当前文件」开关:监听当前文件外部变更并触发自动重载。**`readerEditMode`** 为 true 时不注册监听;用户在编辑态保存也不会触发自动重载(避免覆盖未同步到只读管线的 Monaco 缓冲区)。
- **`useAppShellThemeWatch.ts`**:主题切换:根节点 class、编辑器主题、原生主题 IPC。
- **`useAppWindowBindings.ts`**:窗口挂载/卸载、可配置快捷键(`shortcutBindings`)、拖放与主进程 IPC 等绑定;查找栏聚焦时让出 **`Up`/`Down`** 滚行快捷键(见 [基础功能.md](./基础功能.md) → **「Monaco 查找栏」**)。
- **拖放**:命中带 `data-drop-zone="file-list"` 的节点时向侧栏列表**追加**文件;落在其它区域时对拖入路径取「最外层首个」支持的文件并**打开**(与 `utils/dragDropFsPaths.ts` 配合)。
- **全屏边缘**:`document` 上 `mousemove` 驱动全屏边缘唤起(具体逻辑在 `useAppReaderChrome`)。
- **流与进度**:订阅 `file:stream-*`,在流结束并完成滚动/恢复阅读位置后置 `readingProgressSynced`。
- **卸载落盘**:`pagehide` / `beforeunload` 时落盘会话与设置(与「清除缓存」防回写配合)。
- **`useReaderSidebarLists.ts`**:侧栏文件/章节/书签虚拟列表、过滤与滚动同步;文件列表按 **`fileCategory`** 筛选、按 **`fileSort`** 排序,与项上 `category` / `addedAt` 等字段合并展示。章节列表视口联动滚动受 **`suppressChapterListAutoScroll`** 抑制(进/出编辑、切换压缩空行等);须在 **`syncChaptersAfterViewportSettled`** 的 `finally` 或流错误路径中恢复,否则换章不再居中当前章。
- **`useReaderInlineSearch.ts`**:阅读区内联搜索:关键词匹配、结果列表、当前命中定位与导航。
- **`useReaderAnnotations.ts`**:阅读器**划线 / 笔记**状态机:选区浮动工具条(`ReaderSelectionToolbar`)、笔记输入面板(`ReaderNoteInputPanel`)、标注 hit 索引与视口 inline 装饰(`readerAnnotationDecor.ts`);`emitUpsert` / `emitRemove` 上抛至 `App.vue` 写 **`readerAnnotations`**。**点击已有标注**时绑定 draft 而不改选区;**移除划线**且无笔记时移除整条记录。**问 AI** 填入侧栏引用后不关闭工具条(`suppressToolbarUntilMs`)。色盘 **`colorIndex`** 按当前标注色列表长度解析,越界回退**最后一色**(`clampLineationColorIndex`)。**编辑模式**下不建 hit 索引、不挂 Monaco 装饰并关闭工具条;**只读**下 **`rebuildAnnotationIndex`** 经 **`buildAnnotationHitsByDisplayLine`** 按物理列映射展示列后同步视口装饰。侧栏/导出引用原文统一走 **`resolveAnnotationDisplayQuote`**(见 **「阅读器标注与笔记」**)。
- **`useConnectionTest.ts`**:设置页 **测试连接** 按钮状态(`idle` / `pending` / `ok` / `fail`);配置指纹变更后重置;供 **`AppConnectionTestButton.vue`** 使用。
- **`useFileListCategorySort.ts`**:文件列表:分类下拉(`AppCustomSelect`)的固定项/滚动项/计数与触发器文案;`FileSortMode` 与 `constants/fileCategories` 对齐。
- **`useFileListSelection.ts`**:文件列表「编辑模式」:多选路径、`Ctrl+A` / 反选、与列表焦点区配合;选中集随列表变化裁剪。
- **`useFileListMenus.ts`**:文件列表右键菜单、编辑模式菜单、**分类浮层**(`CategoryPickerMenu`)坐标与 `setFilesCategory` 派发。
- **`useTxtStreamPipeline.ts`**:大文件流式解析与只读展示。
- 流式阶段**仅累积物理行**;字数/总行在格式化完成后写入 ref;展示格式化集中在 **`reader/readerDisplayPipeline.ts`** 的 **`formatPhysicalLinesForReader`** / **`applyReaderDisplayFromPhysicalLines`**;格式化后再经 **`services/textConvertApply.ts`** 的 **`applyTextDisplayConverts`** 做展示层转换(见 [基础功能.md](./基础功能.md) → **「简繁与全半角转换」**)。
- 物理行/显示行映射、**`getDisplayLineContent`**(优先读 Monaco 当前行,回退 **`lastFormattedDisplayLines`**;编辑态无模型时返回空串)、**`physicalSearchRangeToDisplayColumns`**(侧栏搜索命中 → Monaco 列,经 **`annotationColumnMapOptions`** + **`physicalColumnToDisplayColumn`**,与标注列映射一致;只读且 **`leadIndentFullWidth`** 时计入行首全角缩进,章节标题行豁免;**`readerEditMode`** 为 true 时列 1:1)。
- 插图锚点删行后同步收缩映射表。
- 编辑态 **`resyncMirrorFromReader`** 将 Monaco 全文同步为 `physicalLineContents`(供底栏统计等);**只读侧栏搜索**改扫展示层(**`getDisplayLineContent`**),不经此镜像。
- **`useAiChapterPlainTextBridge.ts`**:订阅 `window.colorTxt.onChapterPlainRequest`,调用 **`getChapterPlainTextByIndex`**(`currentChapterPlainText.ts`)后 `replyChapterPlainText`。
- **`useAiFoldContentSelectAll.ts`**:AI 阅读助手:工具调用 / 思考等折叠区正文的「全选」与键盘选择(与 `AiAssistantDetailsFold` 等配合)。
- **`useCharacterCardTilt.ts`**:角色卡 **3D 倾斜** 与光泽联动(思路参考 [pokemon-cards-css](https://github.com/simeydotme/pokemon-cards-css))。**`rotateX` / `rotateY`** 为唯一驱动;每帧由旋转反推 **`--char-pointer-*`**、**`--char-background-*`**、**`--char-card-opacity`** 等 CSS 变量。指针跟手用 **`CARD_SPRING_FOLLOW_ROTATE`**,移出卡片用 **`CARD_SPRING_SNAP_ROTATE`** 回正(带轻微过冲)。**`textureEffect === 'off'`** 或放大过渡未完成时禁用倾斜。
- **`useCharacterCardPopoverZoom.ts`**:角标「查看大图」:**原位**将同一张卡 **`Teleport` 到 `body`**,**`cardShell`(translater)** 负责 `translate3d` + `scale`,**`card__tilt`(rotator)** 负责 **`--char-popover-rotate-y`**(打开 360°→0°,关闭 0°→360° 后 instant 归 0°)。列表格内留 **`cardShellPlaceholder`** 占位;其它卡半透明且 **`pointer-events: none`**。放大激活约 100ms 后 **`tilt.resetIdle()`** 收掉悬停倾斜(对齐参考实现的 `interactEnd`)。
- **`useCharacterRosterReorder.ts`**:侧栏角色卡 **网格拖动排序**(依赖 **`sortablejs`**)。`forceFallback` + **`fallbackTolerance: 8`** 区分点击翻面与拖动;**`.cardShell.flipped`** 与角标按钮等经 **`filter`** 排除(背面不可拖,原因见 [基础功能.md](./基础功能.md) → **「列表拖动排序」→「为何不支持背面拖动排序」**)。拖动层 **`cardGridSlot--ghost` / `--drag`**,松手 **`characterCardTiltDom.playDragReleaseAnimation`** 直线飞回占位;顺序变更经 **`onCommit`** 写回 **`file.meta.characterRoster`**。
- **`useSortableReorder.ts`**:设置/配色等 **表格行或 div 行** 的通用 Sortable 封装:仅 **`.sortableRowHandle`**(图标 **`icons.move`** / `move.svg`)可发起拖动;`onEnd` 回调 **`fromIndex` / `toIndex`**;弹窗 **`active`**、项数 **`itemCount`** 变化时重建实例。详见 **「列表拖动排序(SortableJS)」**。
- **`useAppTimedScroll.ts`**:顶栏 **定时滚动**:`setInterval` 驱动 **`scrollByPageStep` / `scrollByLineStep`**;与朗读、编辑、加载、到底互斥;设置变更时重启计时器(见 **「定时滚动」**)。
- **`useAppHeaderLayout.ts`**:监听 **`window.innerWidth`**,输出 **`compactFontToolbar` / `compactFormatToolbar`**(断点 **1030 / 830** px),供 **`AppHeader`** 决定字体组 / 格式组是否收入 **`MoreMenu`**(见 **「顶栏 UI」**)。
###### `constants/`
- **`appUi.ts`**:UI 常量:存储 key、侧栏宽度、字号/行高上下限与步进、`default*` 出厂默认等(无本地设置或与 `persistKey` 字段缺失时;见下文「阅读器字号与行高」「界面与阅读偏好默认值」);re-export `readerPalette` 的 `applyReaderSurfaceToDocument` 等。
- **`readerPalette.ts`**:阅读器表面色(背景、章节标题、Monaco txtr token)默认值与合并;**`ReaderSurfaceColorEnabled`** 控制引号内/括号内/标点/特殊标记/数字/字母是否使用独立色(默认全开,关闭时 **`resolveEffectiveReaderPalette`** 回退为正文色,色值仍保留);用户覆盖存 **`readerPaletteOverridesLight` / `readerPaletteOverridesDark`** 与 **`readerPaletteColorEnabledOverridesLight` / `readerPaletteColorEnabledOverridesDark`**(开关仅持久化 `false`);`App.vue` 向 **`ReaderMain`** 传入合并后的**有效色**;`useAppShellThemeWatch` 写入 `html` 的 `--reader-bg`、`--reader-chapter-title`。
- **`highlightColors.ts`**:自定义高亮色:默认亮/暗两套 `#RRGGBB` 数组、`MIN_HIGHLIGHT_COLORS`(至少 3 色)、`parseHighlightColorsArray` / `mergeHighlightColors` 等与设置持久化配合。
- **`lineationColors.ts`**:划线标注色(与高亮词独立):默认亮/暗两套、`MIN_LINEATION_COLORS`(至少 3 色)、`parseLineationColorsArray` / `mergeLineationColors`。
- **`annotationColors.ts`**:标注色下标解析(`parseLineationColorIndexRaw`)、**越界回退最后一色**(`clampLineationColorIndex`)、三种线型上次选色 **`lineationLastColors`**(`marker` / `wavy` / `straight`)。
- **`fileCategories.ts`**:侧栏文件分类:`FileCategoryDefinition`、`FileSortMode`、筛选常量(`__all__` / `__uncategorized__`)、默认分类色表、`parseFileCategoryCatalog` 等。
- **`readerSidebarTab.ts`**:侧栏活动栏 tab id:`files` / `chapters` / `bookmarks` / `highlights` / **`notes`** / `aiAssistant` / `character` / `search`。
- **`timedScroll.ts`**:定时滚动 **`TimedScrollSettings`**(`range`: `screen` | `line`,`intervalMs`)、默认值与 **`mergeTimedScrollSettings`** / **`clampTimedScrollIntervalMs`**。
- **`appHeaderLayout.ts`**:顶栏响应式收纳断点 **`HEADER_COMPACT_FONT_BREAKPOINT`**(1030)、**`HEADER_COMPACT_FORMAT_BREAKPOINT`**(830)。
###### `monaco/`
- **`chapterStickyScroll.ts`**:注册 DocumentSymbolProvider 以驱动 Monaco `stickyScroll`(outlineModel)黏性章节大纲;禁用黏性条点击跳转;`refreshStickyChapterScrollWidget` 在大纲/装饰更新后关开 sticky 以套用章节标题样式(与 `--reader-chapter-title`、`1.2em` 一致)。是否启用由设置 **`stickyChapterTitleEnabled`**(**设置 → 阅读 → 启用粘性章节标题**,默认开)与 `ReaderMain` 的 **`streamLoading`** 共同决定 Monaco `stickyScroll.enabled`。
- **`readerEditorOptions.ts`**:阅读器 `create` / `updateOptions` 的选项构建(换行、只读/编辑 chrome、小地图、行号、**`stickyScroll`**(受 **`stickyChapterTitleEnabled`** 控制)等);垂直滚动条:**窗口只读 / 任意编辑** 为 `visible`(常显),**全屏只读** 为 `auto`(失焦淡出)。
- **`readerInlineDecorations.ts`**:章节标题行内装饰;Monaco 主题 chrome(小地图/滚动条/选区/当前行);**`buildChapterMinimapSectionHeaderDecorations`**(编辑态小地图节标题);合并 `readerPalette` 与 **`highlightColors`** 生成 Monarch token 规则;自定义高亮词开启时并入 `txtrHighlightMonarch` 生成的规则。
- **`readerMainMonaco.css`**(由 `ReaderMain` 引入):小地图左侧阴影、滚动条轨道与滑块、概览尺层级(光标标记不被轨道遮挡);全屏时小地图/滚动条/概览尺 `position: fixed` 贴视口右缘(见 **`appShell.css`**)。
- **`readerImageViewZones.ts`**:块级 `` 删行并插 ViewZone;返回删行前行号供流管道同步映射;与 `colortxt://` 本地资源协议衔接。
- **`readerKeyScroll.ts`**:方向键/Page 键滚动。
- **`txtrHighlightMonarch.ts`**:由 `highlightWordsByIndex` 生成 `txtr.customHighlight.{index}` 类 Monarch 规则(更长词优先、同长则更小颜色索引优先;大小写不敏感)。
- **`txtrTextMonarch.ts`**:自定义 Monarch:`txtr-text` 语言;标点/对话/数字等着色;可选注入上述自定义高亮规则。
###### `reader/`
- **`chapterIndex.ts`**:当前视口行号对应的章节下标(二分查找);侧栏书签项上的章节名亦用同一函数按书签行号推断。
- **`lineMapping.ts`**:物理行号与「滤空后显示行」的映射工具。
- **`ebookAnchorLookup.ts`**:电子书内链与压缩空行下的显示行 ↔ 物理行映射。
- **`readerEbookPointer.ts`**:阅读区内电子书内链指针/点击命中辅助。
- **`readerHighlightGeometry.ts`**:自定义高亮词浮动层(`ReaderHighlightFloat`)与**选区标注工具条**(`ReaderSelectionToolbar`)的几何与布局计算。
- **`readerAnnotationDecor.ts`**:标注视口 **inline 装饰**(马克笔 / 波浪线 / 直线)与按当前标注色表生成的**动态 CSS 规则**;**`buildAnnotationHitsByDisplayLine`** 将存盘物理列映射为 Monaco 展示列并建 hit 索引供点击命中与 **`getAnnotationQuoteFromHits`** 截取原文(列映射逻辑在 **`readerAnnotations.ts`**,与搜索跳转一致)。
###### `ebook/`
- **目录**:电子书 → Markdown 的顶层入口;格式解析、目录注入与写出在子目录 **`convert/`**(与 `shared/ebookExtensions.ts` 扩展名一致);细节见下文 **「电子书解析与转换」**。
- **`ebookFormat.ts`**:是否电子书路径、与 TXT / `.md` 合并的「支持书籍路径」、输出基名与文件名净化等。
- **`ebookTitleMatch.ts`**:`plainTextForEbookTitleMatch` 等,目录标题与正文行匹配用纯文本提取(去 span / ATX / 内链)。
- **`pathUtils.ts`**:路径拼接与规范化(POSIX 风格片段,供转换与资源相对路径)。
- **`yieldToUi.ts`**:长解析中分段 `await`,避免主线程长时间阻塞。
###### `ebook/convert/`
- **`convertEbookToMarkdown.ts`**:按扩展名调度各解析器;`ensureEbookMarkdown`:严格 meta 缓存、`findReconciledConvertedMd` 和解查找、写出 `{basename}.md`。
- **`ebookTypes.ts`**:转换产物类型(如 `EbookMarkdownArtifacts`:正文 + 可选 `imageWrites`)。
- **`parseEpub.ts`**:EPUB(ZIP)解析与转换;可尝试将 ZIP 当 EPUB 处理。
- **`ebookTocAnchorInjection.ts`**:各格式嵌入目录注入 ATX `#` / `##` 与 ``;`queueTocHeadingMutations`、`resolveTocInjectLineIdx`(仅精确标题匹配)。
- **`ebookSpineLineMatch.ts`**:spine 节范围、`findTitleLineInSpineSection`(整行与目录标题完全一致)、`applyLineMutations`。
- **`ebookTocTypes.ts`**:`EmbeddedTocEntry`、`dedupeEmbeddedTocEntries`、`flattenFoliateStyleTocTree`。
- **`ebookEpubNav.ts`**:EPUB `nav` / NCX 展平为 `EmbeddedTocEntry`;href → `epub-NNNN#fragment` 时相对 **OPF 目录**解析(见下文「EPUB 目录 href 映射」)。
- **`ebookMarkdownEmit.ts`**:`EbookMarkdownFragmentRegistry`、``、`formatMdInternalLink`、`atxHeadingPrefix`。
- **`ebookFootnoteLinkFragments.ts`**:脚注 noteref 同行尾部回跳 `fr_*` span。
- **`ebookStemOnlyMdLinks.ts`**:无可见文案的 stem 内链 `[]` 形态。
- **`ebookLinkIconHeuristics.ts`**:链接图标 vs 块级插图结构判定。
- **`parseMobi.ts`**:MOBI / AZW3(KF8):经 `mobi/foliateMobi` 抽取;`injectFoliateMobiTocIntoLines` 注入目录(`book.toc` 或 NCX 回退)。
- **`parsePdf.ts`**:PDF:`pdfjs-dist` 文本层;`getOutline()` 书签大纲 → `injectPdfOutlineIntoLines`。
- **`parseFb2.ts`**:FB2 / FBZ(ZIP 包 FB2)解析与转换。
- **`parseChm.ts`**:CHM:目录与 HTML 遍历、插图写出;依赖 `chm/` 解压与读取。
- **`chm/chmArchive.ts`**:CHM 文件表、块定位与原始块读取。
- **`chm/lzxDecode.ts`**:LZX 流解压(CHM 存储块)。
- **`mobi/foliateMobi.js`**:Foliate MOBI 引擎(打包进渲染层)。
- **`mobi/foliateMobi.d.ts`**:上述脚本的 TypeScript 声明。
###### `ai/`
- **`buildBookVectorIndex.ts`**:按章节切块,经 preload 调用嵌入与向量索引相关 IPC 建库(与主进程 `registerAiIpc` / `ai/rag/vectorDb` 等配合)。
- **`embeddingReady.ts`**:**`getBuiltinEmbeddingBlockMessage`**:内置来源未下载时阻止建索引并提示去设置页下载。
###### `aiAssistant/`
- **`aiAssistantTypes.ts`**:UI 消息 / 工具条 / 思考块、`tokenEstimate` / `tokenUsage` 信息条等类型。
- **`aiAssistantSegments.ts`**:助手消息分段与工具引用交错。
- **`aiAssistantPlainText.ts`**:从 UI 模型提取可复制纯文本。
- **`aiAssistantDbMessages.ts`**:SQLite 消息行与 UI 结构互转;助手 `payload` 可含 `reasoning`、`tokenUsage`、`tokenUsageAvailable`;历史加载时在助手气泡后插入 **`tokenUsage` 角色消息**(由 **`AiTokenUsageBanner`** 渲染,受全局 **`showTokenUsage`** 控制)。
- **`aiAssistantHistoryFormat.ts`**:历史快照格式相关。
- **`aiAssistantExport.ts`**:对话导出(文件保存走主进程 `ai:export:save`)。
###### `directives/`
- **`aiStickScroll.ts`**:折叠区粘性滚底等(供 AI 助手详情折叠组件使用)。
###### `services/`
- **`appDialog.ts`**:队列式应用内对话框:`appAlert` / `appConfirm` / `appPrompt`(`appDialogModel` 队列);由 `AppDialogHost.vue` 渲染。
- **`appToast.ts`**:顶部非阻塞 Toast(`appToast` / `dismissAppToast` / `clearAllAppToasts`);默认 `kind: none`(无图标);`white-space: pre-line` 支持 `\n`;由 `AppToastHost.vue` 渲染。
- **`fileListService.ts`**:目录选择、txt 列表合并与规范化;`TxtFileItem` 含可选 **`category`**、**`addedAt`**(「添加时间」排序);分类重命名/删除时同步列表项。
- **`fileOpenService.ts`**:打开文件前的校验与恢复行号解析。
- **`physicalLineStream.ts`**:按换行切分流式块,处理跨 chunk 的不完整行。
- **`shortcutRegistry.ts`**:快捷键动作 ID、默认 Electron 快捷键、窗口/全局作用域。
- **`shortcutUtils.ts`**:快捷键规范化、物理键位解析(`code` 优先)、展示文案、冲突检测。
- **`shortcutService.ts`**:窗口级快捷键监听:按持久化绑定匹配并派发动作;**`EDIT_MODE_MONACO_DEFERRED_ACTIONS`**(编辑态 Monaco 内让出滚屏/查找等);**`VOICE_READ_SCROLL_BLOCKED_ACTIONS`**(朗读中吞掉部分动作)。详见 [基础功能.md](./基础功能.md) → **「快捷键」**、**「Monaco 查找栏」**。
- **`textConvertApply.ts`**:阅读展示层与编辑态全文转换编排:**`applyTextDisplayConverts`**(简繁经 preload **`convertTextOpenCc`**,字母/数字用 **`@shared/textWidthConvert`**);**`applyTextDisplayConvertsToHighlightWordsByIndex`** 供 **`refreshReaderHighlightDisplayLayer`** 将侧栏高亮词条转为展示层文本;编辑态分项 **`applyTextConvertZh`** / **`applyTextConvertLetters`** / **`applyTextConvertDigits`**。
###### `stores/`
- **`cacheStore.ts`**:localStorage:`PersistedSettingsData` / 会话快照等解析与校验(含 **`fileCategory` / `fileSort` / `fileCategoryCatalog`**)。
- **`fileMetaStore.ts`**:单文件元数据:书签、**`readerAnnotations`**、末行/进度等;与 `colorTxt.file.meta` 同步。
- **`recentHistoryStore.ts`**:最近打开文件列表的持久化与更新。
###### `utils/`
- **`color.ts`**:十六进制与 RGB/HSV 互转、`normalizeLooseHex6` 等;供 `HexColorPickerField` 取色。
- **`format.ts`**:字数、文件大小等展示用格式化。
- **`fontFamilyCss.ts`**:字体族名转 CSS `font-family` 片段(引号与栈拼接,供字体选择等复用)。
- **`presetFontDefinitions.ts`**:预设字体:各平台族名栈、菜单标签、与持久化字体的预设匹配(见「预设字体与平台映射」)。
- **`dragDropFsPaths.ts`**:从拖放 `DataTransfer` 解析文件系统路径(供窗口级 drop 分流)。
- **`fileListPanelDisplay.ts`**:侧栏文件行左边框色、是否在「全部」筛选下显示分类色条等展示逻辑。
- **`modalStack.ts`**:弹窗层叠与 ESC 关闭顺序。
- **`defaultCacheDirs.ts`**:与 preload 对齐的默认路径:`resolveDefaultEbookConvertOutputDirSync`、`resolveDefaultCharacterPortraitCacheDirSync`(`userData` + `@shared` 子目录名)。
- **`fullscreenHeaderFloat.ts`**:指针是否落在全屏顶栏相关浮层子树(与 `constants/appUi` 中 `FULLSCREEN_HEADER_FLOAT_SELECTOR` 配合)。
- **`fullscreenSidebarFloat.ts`**:侧栏 Teleport 浮层命中检测(与 `FULLSCREEN_SIDEBAR_FLOAT_SELECTOR` 等配合)。
- **`aiBookHash.ts`**:书籍内容哈希(与主进程 `ai/rag/bookHash.ts` 算法一致,用于向量库 `book_hash`)。
- **`aiChunkBook.ts`**:纯文本按 token 目标切块(与 `AIConfig` 中 chunk 字段语义对齐)。
- **`currentChapterPlainText.ts`**:按 `chapterIndex` 从阅读器展示层切片(标题行至下一章前,与侧栏章字数一致;`HARD_CAP` 512_000),供 **`useAiChapterPlainTextBridge`** 与 `bookMeta` 装配。
- **`readerSurroundingPlainText.ts`**:视口附近节选(注入 `AIAgentBookMeta.surroundingText`)。
- **`aiMarkdownMarkedSetup.ts`**:`marked.use(marked-katex-extension)`:统一导出配置好的 `marked`(助手 Markdown 入口)。
- **`aiMarkdownMarkedPrep.ts`**:助手消息正文预处理再交给 marked。
- **`aiMarkdownChapterRef.ts`**:章节引用 token 的归一化(`(ch=a,b)`、`(ch=a-b)`、序号后说明外移等)、助手回复链接化(`AiMarkdown`)、导图展示时替换为章节标题(`substituteAiChapterMarkersWithTitles`);跳转按钮 hover **`title`** 为章节名。
- **`aiToolFoldBody.ts`**:工具折叠区正文 HTML 辅助;将进度文案中的 **`当前进度:M/N`** 包为 `.aiDigestProgressFrac`(warning 加粗)。
- **`readerAnnotations.ts`**:标注**物理行 + 物理列**存盘区间 ↔ Monaco 展示范围(**`physicalRangeToMonacoRange` / `monacoRangeToPhysicalRange`**,**`annotationColumnMapOptions`** 控制行首缩进列偏移);**`resolveAnnotationDisplayQuote`** 统一侧栏/导出/存盘 **`displayText`** 的 live 原文(hits → Monaco → 展示行 → **`text`**);**`validateAnnotationAgainstPhysicalSource`** 仅比对物理区间 slice 与 **`text`**(与压缩空行/缩进/转换无关);旧版展示列/展示行 **`migrateLegacyAnnotationToPhysicalColumns`**;列表行 **`buildAnnotationListRows`**、**`groupAnnotationListRowsByChapter`**、`normalizeReaderAnnotations`。
- **`readerAnnotationExport.ts`**:标注 **JSON**(`schemaVersion: 1`)与 **Markdown** 导出/导入;默认文件名 `notes-{日期}-{书名}.md|json`;Markdown 按章节 `##` 分组、笔记 **`💡`** + 原文 blockquote、纯划线 **`✨`**、文末 `*导出于 …*`;导出原文经可选 **`resolveQuoteText`**(默认 **`ann.text`**,运行时由 `App.vue` 注入 **`resolveAnnotationDisplayQuote`**)。
##### `src/shared/`
- **`packageDerived.ts`**:从 package 信息派生的共享元数据(主/渲染共用)。
- **`ebookExtensions.ts`**:电子书扩展名常量与壳层打开路径判定。
- **`ebookConvertPaths.ts`**:默认转换输出子目录名 `ConvertedTxt`(`userData/ConvertedTxt`,与 preload 拼接一致)。
- **`aiTypes.ts`**:AI 共享类型与 **`defaultAIConfig`**(含 **`showTokenUsage`**、**`chat.tokenPricePerMillion`**、**`chat.maxToolRounds`**、**`chat.systemPromptExtra*`**、**`embedding.remoteEmbedBatchSize`**、默认对话 Base URL 等)。
- `AIConfig`、对话/嵌入端点;**文生图**(`AITxt2ImgConfig`,含 Agnes / OpenAI 兼容等 **`backend`**);Agent 载荷;角色画风/抽取结果(含 **`aliases`**)等。
- `defaultAIConfig` 与配置迁移常量。
- **`aiTxt2ImgIpc.ts`**:渲染进程调用 `ai:txt2img` 时的请求草稿与返回结果类型(含 **`testConnection`**,不出图)。
- **`txt2ImgBackend.ts`**:**`getTxt2ImgPromptFamily`**(`sd` / `natural`)、**`resolveTxt2ImgSize`**、各后端默认云端模型等。
- **`txt2ImgCloudSizePresets.ts`**:**`txt2ImgSupportsCustomSize`**(本地 WebUI / ComfyUI / **自定义 OpenAI 兼容 Images** 为自由宽高 **64–2048**);其余云端后端为固定尺寸档;切换服务商时 **`applyTxt2ImgSizeForBackendSwitch`**(参考 **512×768**,在比例足够接近的档位中选像素最少,利于立绘省额度)。
- **`txt2ImgCloudModelPresets.ts`**:各 **`backend`** 的模型 ID 建议(新→旧);万相 2.5+ / 2.6+ 高分辨率与协议分支判定。
- **`txt2ImgOpenAiQuality.ts`**:OpenAI Images 画质枚举与设置页中文标签。
- **`aiSkills.ts`**:内置技能元数据、用户覆盖结构、自定义技能 `AiCustomSkill` 及合并/规范化工具。
- **`aiAgentSkillToolNames.ts`**:Agent 可调技能名常量(与主进程 **`ai/chat/agentTools.ts`** 等对齐)。
- **`aiChapterRefPrompt.ts`**:助手回复中章节引用类 token 的提示词约定(与 `aiMarkdownChapterRef.ts` 配合)。
- **`apiEndpointPresets.ts`**:对话 **`CHAT_API_PROVIDER_PRESETS`**(服务商名 + 官方 Base URL 两行下拉;含 **MiniMax**、**小米 MiMo**、**Agnes AI**、OpenRouter、Gemini OpenAI 兼容、**「自定义 OpenAI 兼容服务」** 等);**`findChatProviderPresetByBaseUrl`** 与接口地址联动(手改地址可反推服务商,清空后保持「自定义」)。**`applyOpenAiCompatAuthHeaders`**:MiMo 官方 API 使用 **`api-key`** 请求头,其余默认 Bearer。**`mimoApiLikely`** / **`minimaxApiLikely`** / **`agnesApiLikely`** 供深度思考等按 URL 识别网关。**`chatModelPresets.ts`**:MiMo 拉取模型成功后过滤 TTS/ASR 并按 `vX.Y` 版本新→旧排序(**无**本地预设回退列表)。文生图 **`TXT2IMG_BACKEND_PRESETS`**(含 **MiniMax** `minimax_images`、**Agnes AI** `agnes_images`;服务商名 + 默认 Base URL 两行下拉;与 **`AITxt2ImgConfig.backend`** 一致);选中服务商写入默认地址与默认云端模型,**不**按地址反推服务商。语音朗读 / 万相文案统一为 **「阿里云通义(DashScope)」**(**`DASHSCOPE_PLATFORM_LABEL`** 等);MiniMax / MiMo 对话/文生图/TTS 密钥在应用内**分开存储**(见 [语音朗读](./语音朗读.md)、**「文生图服务商」**)。
- **`aiSystemPromptPresets.ts`**:对话方案 **附加系统提示词** 内置预设(**无** / **虚构文学分析** / **摘录与客观描述** / **自定义**);**`systemPromptExtraMode`** + **`systemPromptExtra`** 文本;编辑内置预设正文后自动切为 **自定义**。
- **`characterAliases.ts`**:角色 **别名** 输入解析(中英文逗号/竖线)、与用户填写/检索识别结果合并去重;主进程立绘与 **AI 检索** 用别名扩展 RAG 查询。
- **`builtinEmbeddingModels.ts`**:内置模型清单(默认 **`bge-small-zh-v1.5`** 512 维、**`multilingual-e5-small`** 384 维);**`DEFAULT_HF_REMOTE_HOST`**(默认 **`https://hf-mirror.com`**,可清空改用官方 Hugging Face)。
- **`builtinEmbeddingIpc.ts`**:向主进程传递当前 **`builtinModel`** 与配置快照(下载/加载/清缓存 IPC)。
- **`aiDataPaths.ts`**:与主进程 **`ai/infra/paths`** 一致的默认子目录名(`ai/data`、`ai/model-cache`),供渲染层 placeholder。
- **`aiTokenUsage.ts`**:`extractUsageFromChatJson`、`addTokenUsage`;**`readPromptCacheHitTokens`**(`prompt_cache_hit_tokens`、`prompt_tokens_details.cached_tokens`、`cache_read_input_tokens` 等);**`computeTokenUsageCost`** / **`formatTokenUsageCost`**(去尾零);`estimateAgentTurnTokens`;**`formatTokenUsageSummaryLine`**(可自定义标签);**`formatTokenUsageActualLine`**(助手对话「本次对话消耗 Token」)。
- **`characterTypes.ts`**:侧栏「角色」:`CharacterRosterEntry`、`CharacterBookStylePersisted`、`CharacterGender`(按书存 `file.meta`);角色 **`voiceReadVoiceId`** 与试听样句(**`voiceReadSampleLine`** / **`voiceReadSampleQuotes`**)供多音色朗读。
- **`characterPortraitPaths.ts`**:立绘缓存根默认子目录名 `CharacterPortrait`、按书名净化目录段、立绘/草稿/临时 PNG 文件名与绝对路径拼接。
- **`characterCardTextureEffects.ts`**:角色卡 **闪卡纹理** 效果 id、菜单文案(`CHARACTER_CARD_TEXTURE_EFFECTS`)、**`DEFAULT_CHARACTER_CARD_TEXTURE_EFFECT`**(默认 **`soft` / 细腻光泽**)、**`normalizeCharacterCardTextureEffect`**(无效或已移除 id 回退默认)。可选 **`dividerBefore`** 控制子菜单项上方分隔线。
- **`chapterMatchBuiltinPatterns.ts`**:章节匹配三条内置正则(与 `renderer/chapter.ts` 同源)。
- **`chapterMatchAgentTurn.ts`**:判定 Agent 本轮是否以「生成/调整章节匹配规则」为主(配合 `chapter-match-rules` 技能)。
- **`colorTxtOpenSaveDialog.ts`**:打开/保存对话框选项类型(主进程 `dialogInvoke` 与 preload 对齐)。
- **`colorTxtShowMessageBox.ts`**:`showMessageBox` 选项类型(主进程 `messageBoxInvoke` 与 preload 对齐)。
- **`textConvertTypes.ts`**:顶栏「转换」菜单项与模式类型(**`TextConvertZhMode`**、**`TextConvertWidthMode`**)、**`TEXT_CONVERT_*_MENU`** 常量、**`resolveOpenCcConfig`**(澳门 **`mo2s`/`s2mo`** 复用香港 **`hk2s`/`s2hk`**)、持久化解析 **`parseTextConvert*`**。
- **`textWidthConvert.ts`**:字母(**`A–Z`/`a–z`**)与数字(**`0–9`**)全角 ↔ 半角;不处理其它 Unicode 全角符号。
#### `src/main/`(主进程)
**`index.ts`**
- 组装主进程能力:`createMainWindowFactory`(窗口创建)、`registerMainIpcHandlers`(业务 IPC)、`setupLaunchTxtHandlers`(启动 txt / 单实例)。
- `app.whenReady()` 后调用 `setupAutoUpdater()`,并根据启动参数 / macOS `open-file` 队列决定首个窗口是否直接打开某个 `.txt`;并调用 `registerGlobalShortcuts()`(见 `globalShortcuts.ts`)。
- `will-quit` 时调用 `unregisterGlobalShortcuts()`,避免进程退出后仍占用系统快捷键表。
- `activate`:macOS 点击 Dock 图标且无窗口时重建主窗口。
- `window-all-closed`:全部窗口关闭后 `markAppQuittingForClose()` 并 `app.quit()`(含 macOS);配合 `windowCloseGuard` 避免 Cmd+Q / 菜单退出时关窗拦截导致进程残留。
**`globalShortcuts.ts`**
- 集中注册 / 注销主进程 `globalShortcut`;后续新增系统级快捷键时在本文件扩展 `registerGlobalShortcuts` / `unregisterGlobalShortcuts` 即可。
- **阅读器显隐**:默认 accelerator 为 **Control** + **\`(反引号键)**(`DEFAULT_TOGGLE_VISIBILITY_ACCELERATOR`;macOS 亦为 **Control** 而非 Cmd)在系统范围内触发;用户可在快捷键面板中修改,由 `setToggleVisibilityShortcut` 更新 `currentToggleVisibilityAccelerator` 并重新注册。主窗口 `useAppWindowBindings` 与找书窗口 `useFindBookPanelShortcuts` 启动时都会按持久化设置调用 `setGlobalShortcut`(避免 `--find-book` 独立启动时仍停留在默认热键)。
- **录制快捷键时临时注销**:`suspendGlobalShortcutsForRecording` / `resumeGlobalShortcutsAfterRecording` 在打开编辑弹层时注销当前全局热键、关闭后 `registerGlobalShortcuts()` 恢复,避免「录制组合键」与「已注册的全局热键」冲突。
- **校验与设置**:`validateGlobalShortcut` 用临时注册探测是否可用;`setToggleVisibilityShortcut` 失败时回滚到旧 accelerator。
- **单一状态位**:主进程用 `allWindowsStealthHidden` 维护两种模式:
- **全部显示**(概念上):含正常窗口与最小化窗口(任务栏仍能点到);
- **全部隐身**:所有窗口 `setSkipTaskbar(true)` + `hide()`,任务栏/Dock 上不可见。
- **作用范围**:每次切换都对 `BrowserWindow.getAllWindows()` 中每个未销毁窗口执行同一模式;进入隐身前把各窗口 `isMinimized()` 记入 `minimizeSnapshotByWindowId`,退出隐身时先 `show()` 再按需 `minimize()`,以恢复最小化形态。
- **macOS 程序坞**:与状态位一致。
- 调用 `app.dock.hide()` / `app.dock.show()`(配合 `isVisible()` 避免重复调用)。
- 退出隐身时先同步 Dock 再 `show()` 各窗口。
- `will-quit` 时 `unregisterGlobalShortcuts()` 会在可见性需要时调用 `dock.show()`,避免退出后仍保持隐藏态。
- **Cmd+Q 后图标仍在程序坞**:多数属于 **系统行为而非 Bug**:
- (1) 曾在程序坞图标上右键勾选过「选项 → 保留在程序坞中」,退出后仍会保留为可点击启动的图标;
- (2) 系统设置里若开启「在程序坞中显示最近使用的应用程序」,刚退出的应用会出现在该区域。应用**无权**替用户改写程序坞固定项或系统 Dock 偏好,需用户在程序坞中右键「选项 → 从程序坞中移除」,或在 **系统设置 → 桌面与程序坞** 中关闭上述「最近使用」相关选项(具体文案随 macOS 版本略有差异)。
- 与渲染进程 `services/shortcutService.ts` 中的键盘监听不同:后者仅在窗口聚焦且在前台时生效;本模块为 **Electron 主进程全局快捷键**,即使用户正在其他应用中也触发(若未被系统或其它应用抢占注册)。
**`detectTextEncoding.ts`**
- **职责**:根据文件头字节推断供 **`iconv-lite`** 解码的编码名;**`file:stream`** 与 **`file:readWholeTextFile`** 均经 **`detectTextFileEncoding(path, app.getLocale())`** 调用(实现于 `ipcHandlers.ts` 的 `detectEncoding`)。
- **采样**:最多读取文件头 **64 KiB**(小文件则仅为实际字节数);**不是**采样上限过小,而是短文本本身可供统计的字节过少时 `jschardet` 易误判。
- **判定顺序**(`detectEncodingFromSample`):
1. **BOM**:UTF-8 / UTF-16 LE / UTF-16 BE;
2. **纯 ASCII** → `utf8`;
3. **严格 UTF-8**(`TextDecoder` fatal)→ `utf8`;
4. **`jschardet.detect`**,并结合置信度与字节结构做修正(见下);
5. 高置信度(≥ **0.7**)时采用 chardet 结果(经 `normalizeEncodingName`,如 `gbk` / `gb2312` → `gb18030`);
6. 仍无法确定且字节像 GBK 族 → `gb18030`;否则回退 `utf8`。
- **中文 ANSI(记事本)启发式**(`shouldPreferGbkFamily`):当样本 **< 512 字节**、chardet **置信度 < 0.7**、被判为 ISO-8859-* / Windows-125* 等西欧编码,或 **`app.getLocale()`** 为 `zh-*` 且置信度 < 0.9 时,若非合法 UTF-8 且非 ASCII 段均可解析为 **GBK/GB18030 双字节序列**,则优先 **`gb18030`**(覆盖「仅几字中文 + 英文」的短文件被误判为 `ISO-8859-2` 等情况)。
- **局限**:未识别 Windows「ANSI」标签本身;繁体 Big5(CP950)等与 GBK 字节形态相近时可能仍需用户通过底栏 **「保存为 GB2312」** 等方式显式转码;非中文环境的其它本地代码页亦不在此模块特判。
**`ipcHandlers.ts`**
- **集中注册的 IPC(`ipcMain`)**:`dialog:showOpenDialog` / `showSaveDialog` / **`showMessageBox`**(选项解析见 `dialogInvoke` / `messageBoxInvoke`);`dir:listTxtFiles`(含扫描进度事件)、`file:stat`、`file:watchCurrent`、`fonts:listSystemFonts`、`shell:*`、`fs:*`、`colortxtLocal:registerPath`、`path:toFileUrl`、`file:stream` 等。
- **历史清理**:独立的 **`dialog:confirmClear*`** 等确认 IPC 已不在此注册(`registerMainIpcHandlers` 内仅 **`removeHandler`** 清理旧名,防热重载重复注册);渲染侧改用 **`showMessageBox`** 或应用内 **`appDialog`** 队列。
- **快捷键**:`shortcut:getGlobalToggle`、`shortcut:validateGlobalToggle`、`shortcut:setGlobalToggle`、`shortcut:suspendForRecording`、`shortcut:resumeAfterRecording`(实现见 `globalShortcuts.ts`)。
- **流式读文件(主进程)**:`file:stream` 使用 `createReadStream` + `iconv-lite` 解码,经 `file:stream-*` 向渲染进程推送数据块;编码由 **`detectTextEncoding.ts`** 探测(见上专节)。
- **整文件读写(阅读器编辑)**:**`file:readWholeTextFile`**(一次性读入、**同一套**编码探测后解码为字符串)、**`file:writeTextFile`**(按指定编码整文件写出),与流式读盘并存;见 **「阅读器编辑模式」**。
- **流式读文件(并发与序号)**:每次新流递增 `requestId` 并 `destroy` 上一轮同窗口读流;发送 chunk 前校验序号,避免旧流残留。渲染进程在 `resetSession` 时清空 `activeStreamRequestId` / `activeStreamFilePath`,并在 `onStreamChunk` / `onStreamEnd` / `onStreamError` 中比对 `requestId`,避免快速重复打开同一文件时旧 chunk 混入已重置的解析管道。
- **渲染进程与 Monaco 写入**:主进程仍分块推送;渲染侧 `useTxtStreamPipeline` 对每个 chunk 只累积**物理行**;`onStreamEnd` 后 `flushCarry`,再 **`formatPhysicalLinesForReader`** →(可选)**`applyTextDisplayConverts`** → **`setFullText`**、更新 **`totalCharCount`**、**`setChapters`**(见 [基础功能.md](./基础功能.md) → **「只读展示管线」**、**「简繁与全半角转换」**)。加载中不累加总字数、不匹配章节;底栏进度由各 chunk 的 `readBytes` / `totalBytes` 驱动。
- **简繁转换**:**`registerTextConvertIpcHandlers`**(`registerTextConvertIpc.ts`)注册 **`text-convert:opencc`**;**`textConvertOpenCc.ts`** 经 **`createRequire('opencc')`** 加载 CJS 绑定,配置 JSON 显式指向 **`prebuilds/assets/`**(`electron-rebuild` 后 binding 在 **`build/Release`** 时须修正 assets 路径);打包后路径 **`app.asar` → `app.asar.unpacked`** 供 C++ 读词典。依赖 **`opencc`** 经 **`postinstall` `electron-rebuild`** 与 **`asarUnpack`**;打包裁剪见 **「打包前 node_modules 裁剪」** 中 **`opencc`** 行。
- 目录递归收集 `.txt`:迭代遍历 + `realpath` 去重,避免符号链接成环导致栈溢出。
- 窗口相关:`window:new`、`window:setTitle`、`window:setFullscreen`、`theme:set`(同步原生主题并广播 `theme:sync`)、**`window:getInitialLoadIntent`**(同步,供首屏侧栏 tab)、**`window:shouldRestoreSession`**、**`window:consumePendingOpenTxtPath`** 等。
**`launchTxtHandlers.ts`**
- `app.requestSingleInstanceLock()`:第二实例会把待打开的 `.txt` 路径转发给已运行实例,并聚焦窗口。
- 解析启动参数中的 `.txt` 路径;macOS 额外处理 `open-file` 事件(启动阶段先入队,就绪后再打开)。
**`windowFactory.ts`**
- 创建 `BrowserWindow`:加载开发环境 `ELECTRON_RENDERER_URL` 或打包后的 `renderer/index.html`。
- 处理 `ready-to-show`、全屏切换事件广播、开发环境 DevTools 快捷键拦截等。
- 维护每窗口 **`shouldRestoreSession`**、**`pendingOpenTxt`** 等状态(`getInitialWindowLoadIntent` / 首屏侧栏 tab,见 **「启动与会话:侧栏初始标签」**),并在窗口关闭时清理。
- 窗口 `resize` / `move` / `close` 时触发边界保存(debounce + close 兜底),具体读写逻辑见 `windowBounds.ts`。
**`windowBounds.ts`**
- 将窗口位置与大小持久化到 `app.getPath("userData")/window-bounds.json`,启动时读取并校验是否仍在屏幕工作区内。
- 窗口最小尺寸 **`WINDOW_MIN_WIDTH` / `WINDOW_MIN_HEIGHT`** 均为 **650** px(与顶栏响应式收纳断点配合,见 **「顶栏 UI」**)。
**`updater.ts`**
- `registerUpdaterIpc()`:注册 `app:isPackaged` 与 `updater:*` 等 IPC(开发环境未打包会跳过实际更新流程)。
- `setupAutoUpdater()`:打包环境下配置 `electron-updater` 行为,并向所有窗口广播更新生命周期事件。
**`updaterMessages.ts`**
- 将 `electron-updater` 的 `ERR_UPDATER_*` 及常见 Node 网络错误码映射为中文提示,供主进程在检查更新、下载与 `error` 事件中统一使用。
#### `src/preload/index.ts`(预加载)
- 使用 `contextBridge` 暴露 `window.colorTxt`,封装 `invoke` / `send` / `on`,避免渲染进程直接使用 Node API。
- **文件与流**:文件对话框与目录扫描(含扫描进度订阅)、`file:stat`、流式读文件事件(`file:stream-*`;载荷可含 **`sessionFilePath`** 表示逻辑书路径如电子书原路径)、**`readWholeTextFile` / `writeTextFile`**(阅读器编辑模式整盘读存,见 **「阅读器编辑模式」**)、`watchCurrentFile` / `onCurrentFileDiskChanged`(当前阅读文件磁盘变更)、外链与系统字体列表等。
- **`getUserDataPath`**(`sendSync`)、**`getDefaultEbookConvertOutputDir`**、**`getDefaultCharacterPortraitCacheDir`**(与 `@shared/ebookConvertPaths`、`@shared/characterPortraitPaths` 子目录名一致)。
- **`pathToReadableLocalUrl`**:调用 `colortxtLocal:registerPath`,返回 **`colortxt-local://resource/{uuid}`** 短 URL,供 `
` / 灯箱避免整段 `file://` 过长。
- 破坏性操作确认:部分使用应用内 **`appConfirm` / `appAlert`**(`services/appDialog.ts` → **`AppDialogHost`**);**清除缓存**、**保存时向量维度变更警告**等使用原生 **`window.colorTxt.showMessageBox`**。
- 文件系统操作:`renamePath`(文件重命名)、`removePath` / `emptyDir` / `mkdir` 等。
- 窗口与系统集成:`openNewWindow`、`toggleDevTools`、`quitApp`、`setWindowTitle`、`setFullscreen`,以及全屏/主题相关事件(如 `onFullscreenChanged`、`onThemeSync`)。
- 会话与启动打开:`shouldRestoreSession`、`consumePendingOpenTxtPath`,**`getInitialWindowLoadIntent`**(同步 `window:getInitialLoadIntent`,首屏侧栏 tab,见 **「启动与会话:侧栏初始标签」**),以及 `onOpenTxtFromShell`(命令行/系统关联打开 txt 的路径回调)。
- **应用更新**:`checkForUpdates` / `downloadUpdate` / `quitAndInstall` 及 `onUpdater*` 事件订阅(含 `onUpdaterDownloadProgress`;打包环境下生效)。
- 拖放文件真实路径(`getPathForFile`)。
- **全局快捷键(显隐)**:`getGlobalShortcut`、`validateGlobalShortcut`、`setGlobalShortcut`、`suspendGlobalShortcutsForRecording`、`resumeGlobalShortcutsAfterRecording`(对应主进程 `shortcut:*` IPC)。
- **AI 章节原文(`ragContext`)**:**`onChapterPlainRequest`** / **`replyChapterPlainText`**(`ai:chapter-plain-request` 与一次性 reply 通道);**`window.colorTxt.ai.onAgentEvent`** 订阅 `ai:agent:event`(`reasoning_delta`、`content_delta`、`tool_*`、`token_usage_estimate`、`token_usage_final`、`round_end`、`done`、`error` 等,类型见 `@shared/aiTypes`)。
- **简繁转换**:**`convertTextOpenCc(text, config)`** → **`text-convert:opencc`**(`config` 为 OpenCC 配置基名,如 `s2twp`);见 [基础功能.md](./基础功能.md) → **「简繁与全半角转换」**。
- **语音朗读**:**`voiceReadEdgeTts`**、**`voiceReadSynthesize`**、**`voiceReadListVoices`**、**`voiceReadHealthCheck`**、**`voiceReadAttributeSpeakers`**(类型见 `@shared/voiceReadSynthesisIpc`、`@shared/voiceReadSpeakerIpc`);见 [语音朗读](./语音朗读.md)。
#### `src/renderer/src/components/`(主要 Vue 组件)
表格单元格内换行使用 HTML `
`(下列较长说明已插入换行以便阅读)。
| 文件 | 主要功能 |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AppHeader.vue` | 顶栏:打开文件、书钉/书签、**字体与字号行高**(**`HeaderFontToolbar.vue`**)、**「转换」**与压缩空行/行首缩进/高级换行/内容上色(**`HeaderFormatToolbar.vue`**,见 **`ConvertMenu.vue`**)、**高亮笔**、章节规则、主题、侧栏与全屏、**定时滚动**(`play.svg`,**`语音朗读`** 左侧)、**语音朗读**、查找与更多菜单等;**阅读器编辑**开关、编辑态**保存**与**格式化**(压缩空行/行首缩进/转换);编辑态 **AI 智能排版**见 [AI功能.md](./AI功能.md)。排版进行中或 Diff 预览时禁用编辑开关。
窄窗口时 **`useAppHeaderLayout`** 将字体组 / 格式组收入 **`MoreMenu`** 的 **`#toolbar`** 插槽(断点见 **「顶栏 UI」**)。**定时滚动** 与 **语音朗读** 互斥:一方开启时禁用另一方入口。
从 `App.vue` 接收当前 **`shortcutBindings`** 并传给 `MoreMenu`;**`@open-color-scheme`** 可从高亮菜单进入配色弹窗 |
| `AppOverlays.vue` | 蒙层弹窗:关于、快捷键、设置、配色、章节规则、**添加/编辑书签**(备注框上方章节名 + 正文预览;编辑时 footer 左 **「更新为当前行」**)与更新流等 |
| `AppContextMenu.vue` | 上下文菜单:**`placement`** **`point`**(书签等,`x`/`y` 为视口内左上角,经夹取)或 **`aboveFooterMouseX`**(底栏路径/编码菜单:整块在底栏上方、横向以打开时指针 `clientX` 居中后再夹到窗口内,见 **「底栏」**);支持 **`disabled`** 项、`excludeCloseWithin`(避免重复点触发控件时误判为外侧关闭) |
| `ConvertMenu.vue` | 顶栏 **「转换」** 三级菜单(图标 **`assets/conver.svg`**):**简繁** / **字母** / **数字**;阅读模式标题 **「转换」**(含 **「关」** 与分隔线,选中项作展示层转换并持久化 **`textConvertZh` / `textConvertLetter` / `textConvertDigit`**);编辑模式标题 **「格式化:转换」**(无 **「关」**,点击即对全文一次性转换)。主菜单相对触发按钮 **水平居中**(`left: 50%` + 小三角);子菜单 flyout 打开前估算视口空间,**右侧有空间时优先向右弹出**,否则向左;**`width: max-content`**(`min-width` 约 160px)。菜单项定义见 **`@shared/textConvertTypes`** |
| `AppFooter.vue` | 底栏:路径、加载/阅读进度、字数、大小、编码;**路径与编码**为链式按钮 + 向上弹出菜单,详见 **「底栏」** |
| `ReaderMain.vue` | 阅读区:挂载编辑器与业务逻辑。
引入 **`readerMainMonaco.css`** 覆盖 Monaco 阅读区样式;编辑器静态选项集中在 `monaco/readerEditorOptions.ts`。
章节行内装饰与 **`highlightColors` / `highlightWordsByIndex`** 驱动的 Monarch 与装饰同步;选区添加自定义高亮词、色块选择器(按当前主题高亮色列表);`monacoCustomHighlight` 开关。
**标注**:**`useReaderAnnotations`** 驱动 **`ReaderSelectionToolbar`** / **`ReaderNoteInputPanel`**;**`readerAnnotationDecor`** 视口 inline 装饰与动态 CSS;只读模式下选区/点击标注交互(编辑模式禁用)。
**`ReaderHighlightFloat`** / **`ReaderImageLightbox`**;查找展开时可联动书钉;高亮词列表点击可进入查找;滚动与 probe。
全屏两侧空白滚轮经父组件调用 **`delegateEditorWheelFromBrowserEvent`**。
流式结束经 **`formatPhysicalLinesForReader`** →(可选)**`applyTextDisplayConverts`** 后 **`setFullText`**(见 [基础功能.md](./基础功能.md) → **「只读展示管线」**、**「简繁与全半角转换」**)。**阅读器编辑**:整盘读写、**`applyEditFormat*`**(含 **`applyEditFormatTextConvertZh/Letters/Digits`**)、**`readerEditShowLineNumbers`** / **`readerEditMinimap`**、**`readerEditContentChange`**、**`captureViewportRestoreAnchor`**,见 **「阅读器编辑模式」**;**AI 智能排版 Diff 预览**见 [AI功能.md](./AI功能.md)。
**书签**:**`getBookmarkSaveAnchorDisplayLine`**(与保存锚点、列表跳转一致的「视口上沿 + 一行字高」逻辑行)、**`jumpToBookmarkLine`**(`revealLineNearTop` 后再 `scrollTop -= lineHeight` 为黏性章节条留白)、**`getViewportTopLine`** 等 |
| `ReaderSidebar.vue` | 侧栏容器:活动栏含文件 / 章节 / 书签 / 高亮词 / **笔记** / **AI 助手** / **角色** / 搜索(`constants/readerSidebarTab.ts`)。
高亮词 tab 为彩色图标;**当前书无高亮词**(或未打开文件)时 **`activityTabBtn--mutedColor`** 灰度显示且加深,与其它 tab 视觉权重接近。
挂载 `FileListPanel`、`ChapterListPanel`、`BookmarkListPanel`、`HighlightListPanel`、**`AnnotationListPanel`**、**`AiAssistantPanel`**、**`CharacterSidebarPanel`**、`SearchPanel`。
向文件列表下发 **`fileCategory` / `fileSort` / `fileCategoryCatalog`** 并上抛分类相关事件;**标注**导出/导入/清除失效经 **`ReaderSidebar`** 上抛;**`askAiWithQuote`** 切 tab 并 **`prefillQuotedText`**;与 `useReaderSidebarLists`、`useReaderInlineSearch` 等配合;**阅读器编辑**时章节区可提供刷新章节等入口 |
| `FileListPanel.vue` | 侧栏「文件」:txt/电子书路径列表、**分类筛选**与 **排序**、编辑模式多选、右键与批量改分类。
编辑模式多选复选框复用全局 **`.checkbox`** 样式(`var(--accent)` 勾选色)。
单项右键支持分类/移除/重命名/在新窗口打开/在文件管理器显示(Ctrl+右键附加「清除该文件数据」);筛选在具体分类时 footer 动作为「清空分类」。
`data-drop-zone="file-list"` 标记列表拖放接收区 |
| `ChapterListPanel.vue` | 侧栏「章节」:章节列表、字数开关、跳转当前章 |
| `BookmarkListPanel.vue` | 侧栏「书签」:列表、跳转、编辑与清除;项内 **备注 / 章节名 / 正文预览**(章节由 `pickActiveChapterIdx` 推断;无备注但有章节名时不显示「无备注」占位;正文预览与弹窗同源逻辑);**右键菜单** `Teleport` 到 **`document.body`** 并带 **`data-fullscreen-sidebar-float`**,避免被侧栏 `overflow` 裁切 |
| `HighlightListPanel.vue` | 侧栏「高亮词」:已收藏(全局)与本书词分开展示(收藏在前);收藏/取消收藏、删除(已收藏项须先取消收藏才可删本书项)、点击定位(内联搜索) |
| `AnnotationListPanel.vue` | 侧栏「笔记」:本书 **`readerAnnotations`** 列表;**按章节分组**、**粘性章节标题**(与章节/书签列表样式对齐);项内展示划线类型图标、笔记摘要、原文预览;**右键移除**;footer **更多** 菜单:**导出 Markdown / JSON**、**导入 JSON**、**清除失效笔记**、**清空全部**;点击跳转阅读位置 |
| `SearchPanel.vue` | 侧栏「搜索」:当前文件内搜索、结果列表与命中跳转。
**一行内多次匹配各占一条结果**(与 VS Code 一致);预览仅高亮该条对应的区间;列表行号展示 **`displayLine`**。
跳转列号经 **`physicalSearchRangeToDisplayColumns`**(只读+行首缩进)或编辑态 1:1 物理列;详见 **「侧栏全文搜索」** |
| `FileCategoryFlyoutList.vue` | 文件列表分类子菜单:统一渲染右键分类 flyout 与批量分类入口的选项(含计数) |
| `FontPicker.vue` | 预设字体(跨平台映射,逻辑见 `presetFontDefinitions.ts`)与系统字体列表;下拉面板 **`Teleport` 到 `body`**,经 **`useAnchoredAppShellMenu`**(**`placement: below-center`**)定位;根节点带 **`data-header-float-panel`**,避免在「更多」菜单内操作时误关菜单 |
| `HeaderFontToolbar.vue` | 顶栏字体组:`FontPicker` + 字号 / 行高加减;宽屏由 **`AppHeader`** 直接渲染,窄屏经 **`MoreMenu` `#toolbar`** 插槽收纳 |
| `HeaderFormatToolbar.vue` | 顶栏格式组:**`ConvertMenu`**、压缩空行 / 行首缩进(只读开关或编辑态格式化)、高级换行、内容上色;宽屏 / 窄屏收纳规则同 **`HeaderFontToolbar`** |
| `ChapterRulePanel.vue` / `ChapterRuleEditDialog.vue` | 章节匹配规则列表与编辑。
规则按优先级 **拖动排序**(操作列 **移动** 手柄);表头固定、**仅 tbody 区域滚动**(`ResizeObserver` 同步表头与滚动条占位);顺序写入 **`colorTxt.ui.settings`** 章节规则字段 |
| `ColorSchemeTabBar.vue` | 配色弹窗内页签:**阅读器** / **高亮色** / **标注色** |
| `ColorSchemeReaderPanel.vue` | 「阅读器」页:表面色字段网格 + 实时预览(与 `ColorSchemePanel` 草稿联动)。
除背景色/章节标题/正文外,引号内/括号内/标点/特殊标记/数字/字母各行标签前 **`SwitchToggle`**(默认开);关时预览与 Monaco 使用正文色,**`HexColorPickerField`** 置灰禁用但保留已选色值;无开关项标签前留等宽占位以对齐 |
| `ColorSchemeHighlightPanel.vue` | 「高亮色」页:按槽位编辑 `#RRGGBB`(`HexColorPickerField`)、**拖动排序**(**移动** 手柄)、增删行(不少于 `MIN_HIGHLIGHT_COLORS`)、表格内预览条;槽位标签 **「高亮色 N」** 随顺序更新(草稿行 **`{ id, color }`** + **`:key="row.id"`**) |
| `ColorSchemeLineationPanel.vue` | 「标注色」页:与高亮色页结构类似;槽位标签 **「标注色 N」**;预览条展示三种线型(马克笔 / 波浪线 / 直线);不少于 **`MIN_LINEATION_COLORS`** |
| `ColorSchemePanel.vue` | 配色弹窗容器:`ColorSchemeTabBar` + 上述三面板。
确定时 **`applyReaderPalettes`** 写入亮/暗表面色与 **`colorEnabledLight` / `colorEnabledDark`**,以及 **`applyHighlightColors`**、**`applyLineationColors`** 写回 `App.vue` 并经 `useAppPersistence` 落盘;打开时从 props 同步草稿 |
| `HexColorPickerField.vue` | 单行十六进制颜色 + HSV 取色浮层(智能上下翻转、视口贴边);`draftHex` / `draftEnd` 事件供父组件在弹层打开期间做临时预览;**`disabled`** 时不可打开,若已打开则自动关闭 |
| `MoreMenu.vue` | 更多菜单:可选顶部 **`#toolbar`** 插槽(窄窗口收纳字体 / 格式组,见 **「顶栏 UI」**);最近文件、查找、快捷键、设置、**配色**(动作 `openColorScheme`,默认 **F6**)、检查更新、关于、退出等。
菜单项右侧快捷键文案来自 **`shortcutBindings`**,经 `shortcutUtils.acceleratorToDisplayText` 与快捷键面板及 `shortcutService` 实际生效绑定同步;点击 **`[data-header-float-panel]`** 内浮层(如 **`FontPicker`**)不关闭菜单 |
| `SettingsPanel.vue` | 设置弹窗壳层:**`SettingsTabBar`** + 条件渲染子面板。
footer **「重置当前页」** 按当前 tab 将草稿恢复为应用内默认值(AI 页含 **`aiDataCacheDir`** 默认路径;向量页含内置/远程默认等,见 `resetAiDraft` / `resetVectorModelDraft`)。
**「确定」** 时:向量维度变更提示;**`aiDataCacheDir`** / **`builtinModelCacheDir`** 变更时确认并调用 **`ai:migrateDataCacheRoot`** / **`ai:migrateBuiltinModelCacheRoot`** 再 **`configSet`**。
**「清除缓存」** 见下文「清除缓存」 |
| `SettingsTabBar.vue` | 设置顶栏页签切换;导出 **`SettingsTabId`**(`general` / `reading` / `edit` / `ai` / `vectorModel` / `txt2img` / `skills` / **`voiceRead`**)。
`showAiExtensionTabs` 为 false 时隐藏向量模型 / 角色卡 / 技能三个扩展页签 |
| `SettingsGeneralPanel.vue` | 「常规」:启动恢复上次文件、同步当前文件、历史条数、电子书转换缓存目录、章节最少字数、**清除缓存**按钮(向父组件 `clearCache`) |
| `SettingsReadingPanel.vue` | 「阅读」:字号/行高滑块、压缩空行保留一行、引号/括号跨行匹配、**启用粘性章节标题**、Monaco 平滑滚动、全屏正文区宽度、**定时滚动**(范围 **`RadioGroup`**:一屏/一行;间隔 **`NumericInput`** 毫秒)。
(`monacoCustomHighlight` 来自 props,用于禁用跨行开关提示) |
| `SettingsEditPanel.vue` | 「编辑」:**显示行号**、**启用小地图**、**自动刷新章节列表**;**AI 智能排版**开关见 [AI功能.md](./AI功能.md) |
| `SettingsAIPanel.vue` | 「AI 阅读助手」:总开关;独立 **配置方案** 区块(下拉 + 新建/重命名/删除);**对话模型**(服务商含 **小米 MiMo**,地址、Key、模型、温度、**最大 Token**、**工具调用轮数** `maxToolRounds`、**附加系统提示词** 预设 + 文本框等);切换 **服务商** 时**清空当前模型**与已拉取模型列表缓存(与其它云端一致,需重新拉取/手输);Token 开关与单价(随方案);**`aiDataCacheDir`**;**`AppPullFlashButton`** 拉取聊天模型(MiMo 成功后 **`sortChatModelsForBaseUrl`** 过滤 TTS/ASR 并排新→旧;拉取失败列表为空);**`AppConnectionTestButton`** 极简 `chat/completions` 探活(HTTP **402** / `insufficient_balance` 统一提示「账户余额不足,无法发起对话。」);**生成思维导图**、**词云图词项上限**(`wordcloudMaxWords`);**快速提问**列表(**移动** 手柄拖动排序、`quickQuestionRowIds` 稳定 key、**恢复默认**) |
| `ApiEndpointInput.vue` | 设置页接口地址输入(可选建议列表;对话页建议列表常为空,以服务商下拉为主) |
| `AiTokenUsageBanner.vue` | Token 消耗条(`formatTokenUsageSummaryLine` / `formatTokenUsageActualLine`、可选花费);用于阅读助手、角色检索等(智能排版见 [AI功能.md](./AI功能.md)) |
| `AiIndexProgressBanner.vue` | 建索引 / 向量化进度文案(阅读助手建索引与角色 **AI 检索** 前补索引共用) |
| `SettingsVectorModelPanel.vue` | 「向量模型」:**模型来源**(内置 / 远程)。
**内置**:缓存目录、HF 镜像、模型下拉、下载/清除。
**远程**:服务商 + 地址 + Key + **`AppConnectionTestButton`** + **嵌入模型**(**`ApiEndpointInput`** + 拉取)+ **单次嵌入条数**(`remoteEmbedBatchSize`);切块与 **`ragTopK`** |
| `SettingsTxt2ImgPanel.vue` | 「角色卡」:独立 **配置方案** 区块;**文生图 API 设置**(服务商两行下拉 + 默认地址/模型)+ **接口地址**;云端:**API 密钥**(**`AppConnectionTestButton`** 测试连接,不出图)+ **模型**(**`ApiEndpointInput`** 建议,可手输);**尺寸**:本地 WebUI / ComfyUI / **自定义 OpenAI 兼容 Images** 为宽高数字输入,其余云端为**固定尺寸**下拉;OpenAI 官方 / 兼容代理 **画质**下拉(仅 `openai_images` / `openai_compat_images`);A1111 采样 / 高清修复、Comfy 工作流;**`AppPullFlashButton`** 拉取采样器 / SD 模型。
**角色立绘缓存根目录**(全局,不随文生图方案变) |
| `AppConnectionTestButton.vue` | 设置页共用 **测试连接**(图标 pending/成功/失败;成功不弹框;配置指纹变更后重置);用于 AI 阅读助手、向量模型、角色卡文生图、**语音朗读**(通义 / MiniMax / **MiMo**) |
| `SettingsSkillsPanel.vue` | 「技能」:内置技能开关与覆盖、自定义技能列表;由父级 footer「添加技能」打开 **`SettingsSkillEditModal`** |
| `SettingsVoiceReadPanel.vue` | 「语音朗读」:独立 **朗读方案** 区块(最多 12 套,含引擎、单/多音色、密钥);**引擎**下拉(Edge TTS / 系统语音 / 通义 / MiniMax / **小米 MiMo**);**朗读方案**(单音色 / 旁白·对白多音色);多音色下 **对白引号样式**、**AI 识别**(需 AI 总开关)、**情绪标注**开关(AI 识别开启且引擎支持时;关闭则不向 TTS 传情绪);MiMo 含 **VoiceDesign**(声音描述、智能润色)、**VoiceClone**(参考音频);通义 / MiniMax / MiMo **API 密钥** + **`AppConnectionTestButton`**;模型建议(**`ApiEndpointInput`**);语速/音调;试听预览(切 tab / 关设置时 **`cancelPreview`**)。**密钥与 AI 对话/文生图分开存** |
| `VoiceReadToolbar.vue` | 顶栏朗读工具条:播放/暂停/停止、上一行/下一行、**音量**滑块(运行时调节播放音量;**设置页「音调」** 仍为合成参数)、合成状态;播放中拦截侧栏跳转(见 **`useAppVoiceRead`**);**定时滚动** 开启时禁用朗读入口 |
| `AppCheckbox.vue` | 通用复选框:复用全局 **`.checkbox`** 自定义外观(勾选色 **`var(--accent)`**);用于设置页对白引号样式等多选 |
| `SettingsSkillEditModal.vue` | 自定义技能新建/编辑弹窗 |
| `AppPullFlashButton.vue` | 短时按压态按钮:设置面板内从兼容服务端刷新模型/采样器列表等,完成态闪光反馈 |
| `NumericInput.vue` | 通用数字输入:可选 `min` / `max`、整数模式;默认宽度 **120px**(设置页数值框统一口径) |
| `RadioGroup.vue` | 分段单选按钮组(样式类似 Element UI `el-radio-button`);用于设置页 **定时滚动** 范围等 |
| `RangeSlider.vue` | 通用范围滑块(最小/最大值与步进) |
| `SwitchToggle.vue` | 通用开关控件 |
| `ShortcutPanel.vue` | 快捷键列表与编辑:表格展示、点击录制、Enter 确认、冲突提示、全局热键校验。
录制区为不可编辑聚焦区 + 闪烁光标,避免 IME 上屏 |
| `AboutPanel.vue` | 关于面板 |
| `AppModal.vue` | 通用模态框(与 `modalStack` 配合) |
| `AppUpdateFlow.vue` | 自更新:检查/下载/安装进度、相关弹窗与 `electron-updater` 事件订阅 |
| `IconButton.vue` | 图标按钮 |
| `VirtualList.vue` | 虚拟列表(长列表性能) |
| `AppCustomSelect.vue` | 通用自定义下拉(文件列表左侧 **分类筛选** 触发器、「全部 / 未分类 / 各分类 / 分类管理」与分类色块标记等)。
(用于侧栏文件列表分类入口) |
| `CategoryPickerMenu.vue` | 浮动菜单:编辑模式下为已选文件批量指定分类;单项与 `FileListPanel` 内分类操作共用选项与计数 |
| `FileCategoryManageModal.vue` | **分类管理**弹窗:增删改分类名称与颜色;**拖动排序**(**移动** 手柄,`:key="row.key"`);重命名/删除时通过 `fileListService` 回写列表项 `category` 字段 |
| `PathPickerInput.vue` | 设置等场景下的目录绝对路径输入与主进程文件夹选择器(电子书转换输出目录、**角色立绘缓存根目录**等) |
| `AppDialogHost.vue` | 挂载于 `App.vue`:渲染 `services/appDialog.ts` 队列(`appAlert` / `appConfirm` / `appPrompt`) |
| `AppToastHost.vue` | 挂载于 `App.vue`:渲染 `services/appToast.ts` 的顶部 Toast 列表 |
| `AiAssistantPanel.vue` | 侧栏 AI 阅读助手主面板:会话、输入、`onAgentEvent`、token 预估/实际条插入(受 **`showTokenUsage`** 控制);**`findLiveAgentAssistant`**;**`AiTokenUsageBanner`**。
暴露 **`prefillQuotedText(text)`**:阅读器 **「问 AI」** 填入 blockquote 引用后 **`autosizeComposerInput`** 并 **`scrollComposerToCaretEnd`**(Tab 重新可见时也会 autosize) |
| `AiAssistantChatMessages.vue` | 助手对话消息列表:气泡、工具折叠、思考块(流式未封存显示「正在思考…」);关闭 Token 开关时不插入消耗条 |
| `AiAssistantDetailsFold.vue` | 助手详情区折叠容器(与 `directives/aiStickScroll`、**`useAiFoldContentSelectAll`** 配合) |
| `AiToolFoldBody.vue` | 工具折叠正文;章文压缩进度 **`当前进度:M/N`** 样式(`utils/aiToolFoldBody.ts`) |
| `AiMarkdown.vue` | 助手回复 Markdown 渲染入口(内部用 `aiMarkdownMarkedSetup` / `aiMarkdownMarkedPrep`、章节引用 `aiMarkdownChapterRef`) |
| `CharacterSidebarPanel.vue` | 侧栏「角色」:角色卡网格、**整卡拖动排序**(`useCharacterRosterReorder`,顺序落 **`characterRoster`**)、**AI 检索** 抽屉、**角色立绘生成** 弹窗(预览 **2:3**、表单与底对齐操作钮;**拖放图片**至预览区可设立绘)。
立绘弹窗:**画风 / 角色形象**;**SD 系**显示 **负面描述**(云端不显示);关闭(应用/取消/×)时写入草稿与 **`file.meta`**(`characterBookStyle` + 当前角色 `promptZh`/`negativeZh`)。
**角色别名**:检索/立绘时主进程自动发现并与 **`characterAliases`** 合并,扩展 RAG 查询(侧栏编辑可选手填别名)。
**多音色朗读**:角色编辑可设 **`voiceReadVoiceId`** 与试听样句;启用 **AI 识别** 时朗读自动匹配说话人。
监听 **`aiConfigSyncNonce`**,设置保存后同步文生图服务商 UI;实际出图仍由主进程 **`configGet`** 读最新配置。
检索区 **`AiIndexProgressBanner`**、**`AiTokenUsageBanner`** |
| `CharacterRosterCard.vue` | 单个角色条目卡片(**2:3**):正反面 3D 翻转、立绘与竖排/背面信息;**`charHoloCard`** + **`data-char-texture`** 驱动闪卡层(**`card__shine` / `card__glare`**)。
**`useCharacterCardTilt`** + **`useCharacterCardPopoverZoom`**;列表倾斜幅度约 **40%**,放大后 **100%**;**`:key="entry.id"`** / **`data-entry-id`** 与 Sortable 联动。
背面长文滚动在顶/底边界 **`preventDefault`** 避免带动外层列表;**`:hover` 时 `z-index` 抬高** 避免倾斜遮挡相邻卡 |
| `ReaderHighlightFloat.vue` | 自定义高亮词旁的浮动操作条(依赖 `readerHighlightGeometry.ts` 与 `ReaderMain` 编辑器坐标) |
| `ReaderSelectionToolbar.vue` | 只读选区浮动工具条:复制、高亮词(需 `monacoCustomHighlight`)、马克笔 / 波浪线 / 直线(色块取自当前主题 **`lineationColors`**)、移除划线、**记笔记**(已有笔记时 **`hasNote` 激活态**;有划线时笔记图标跟标注色)、**问 AI**(需 AI 总开关)。几何由 **`readerHighlightGeometry.ts`** 计算 |
| `ReaderNoteInputPanel.vue` | 选区旁笔记输入浮层:新建/编辑 **`note.content`**;与工具条联动打开/关闭;确定后 upsert 标注记录 |
| `ReaderImageLightbox.vue` | 阅读区内插图的灯箱放大(`ReaderMain` 绑定 `imageLightboxSrc`) |
与 **AI 阅读助手 / 向量模型 / 角色卡 / 技能 / 角色侧栏** 强相关的组件说明已集中到 [AI 阅读助手与相关能力](./AI功能.md) →「主要 Vue 组件(AI / 角色与相关设置)」;上表仍保留原行以便与本文目录树对照检索。