# 易标投标工具箱 Client 开发说明

本文面向参与 `client/` 独立客户端开发的成员，目标是快速理解架构边界、目录职责和统一开发风格。

## 技术栈
- Electron：桌面窗口、本地文件系统、配置存储、后续打包入口。
- Vite + React + TypeScript：Renderer 层 UI 与业务流程编排。
- Radix UI：Dialog、Tooltip、Switch 等无样式基础组件。
- CSS：当前使用全局 CSS，不引入 Tailwind，不复用旧 `frontend/` 样式。

## 运行命令
```powershell
cd client
npm install
npm run dev
npm run build
npm audit
```

## 架构边界
- `client/` 是独立桌面客户端，不依赖旧 `frontend/` 和 `backend/` 源码。
- Renderer 不直接访问 Node API，不直接保存 API Key。
- Electron Main 负责本地能力：配置、文件、AI 请求、导出。
- Renderer 通过 `window.yibiao` 调用 preload 暴露的安全桥接 API。
- Prompt builder 只负责组装消息，不直接请求 AI。
- Feature service 只负责本功能业务编排，不直接写 IPC 细节。
- 配置、工作区、草稿等需要持久化的业务数据由 Electron Main 侧读写文件；Renderer 不用 `localStorage` 保存大文本或业务流程状态。

## 目录职责
```text
client/
├── electron/                 # Electron Main / Preload / IPC / Node 服务
│   ├── ipc/                  # IPC 通道注册，只做参数转发
│   ├── services/             # 本地配置、AI、文件、导出服务
│   └── utils/                # Main 侧工具函数
├── src/
│   ├── app/                  # 应用级路由、菜单、Provider
│   ├── components/           # 应用布局组件，例如 AppShell、Sidebar
│   ├── features/             # 业务功能模块
│   ├── shared/               # 跨功能共享能力
│   ├── main.tsx              # Renderer 入口
│   └── styles.css            # 全局样式
└── assets/                   # 客户端图标和静态资源
```

## App 层
- `src/app/menuConfig.ts`：左侧菜单配置，新增一级功能优先改这里。
- `src/app/AppRouter.tsx`：根据 `SectionId` 渲染对应页面。
- `src/app/providers/AppProviders.tsx`：全局 Provider 入口，后续接状态管理或主题时从这里加。

新增主菜单页面时，按以下顺序修改：
- 在 `src/shared/types/navigation.ts` 增加 `SectionId`。
- 在 `src/app/menuConfig.ts` 增加菜单项。
- 在 `src/app/AppRouter.tsx` 增加页面分支。
- 如需工具条动作，由页面根据自身流程和状态渲染 `FloatingToolbar`。

## Feature 层
每个业务功能放在 `src/features/<feature-name>/` 下：
```text
features/<feature-name>/
├── pages/        # 页面级组件
├── components/   # 本功能专用组件
├── hooks/        # 本功能专用 Hook
├── services/     # 本功能流程编排
└── types.ts      # 本功能专用类型
```

当前功能模块：
- `technical-plan`：技术方案，流程统一为上传招标文件、招标文件解析、目录生成、正文生成、扩写改写。
- `knowledge-base`：知识库。
- `duplicate-check`：标书查重。
- `rejection-check`：废标项检查。
- `settings`：独立设置页，由侧栏底部入口进入，管理文本模型、生图模型、文件解析和关于信息。

Feature 开发规则：
- 页面只做 UI 和交互编排，不直接调用 IPC。
- AI 请求通过本 feature 的 `services/` 进入，再调用 `shared/ai`。
- Prompt 统一放 `shared/prompts/`，不要散落在组件里。
- 跨功能类型放 `shared/types/`，仅本功能使用的类型放本功能 `types.ts`。
- 技术方案 Step01 只负责上传招标文件并展示解析出的 Markdown，不做 AI 理解或目录生成。
- 技术方案步骤推进统一由页面级 `FloatingToolbar` 控制，未满足前置条件时禁用“下一步”，不要在步骤页面主体里重复放同语义下一步按钮。
- 页面主体必须占满当前窗口内容区，避免出现全局滚动条；长内容使用页面内部列表、阅读器、工作区或设置页容器自己的滚动条。
- 设置页保持独立页面形态，不改回弹窗；保存入口统一使用页面级 `FloatingToolbar`，不要在各配置卡片底部重复放保存按钮。
- 设置页模型测试按钮放在模型名称输入/选择行内，保存按钮文案统一为 `保存`。

## Shared 层
`src/shared/` 放跨功能复用能力：
- `shared/ai/aiClient.ts`：Renderer 侧 AI 请求门面。
- `shared/ai/stream.ts`：流式文本收集工具。
- `shared/prompts/`：Prompt builder。
- `shared/storage/`：仅保留轻量 Renderer 缓存工具；业务工作区数据优先走 Electron Main 侧存储。
- `shared/types/`：跨功能共享类型。
- `shared/ui/`：跨功能通用 UI，例如 `FloatingToolbar`。
- `shared/utils/`：错误、JSON、ID 等通用工具。

共享层规则：
- 不引用任何 feature 模块，避免循环依赖。
- 类型优先 `interface`，联合类型和泛型工具类型用 `type`。
- 用户可见文案使用中文。

## Electron 层
`electron/` 只使用 CommonJS，保持与 Electron 当前入口一致。

### preload
`electron/preload.cjs` 暴露：
```ts
window.yibiao.config.load() // 返回 ClientConfig
window.yibiao.config.save(config) // 保存 ClientConfig
window.yibiao.config.listModels()
window.yibiao.ai.chat(request)
window.yibiao.ai.requestJson(request)
window.yibiao.ai.testImageModel(config)
window.yibiao.file.importDocument()
window.yibiao.workspace.loadTechnicalPlan()
window.yibiao.workspace.saveTechnicalPlan(state)
window.yibiao.workspace.clearTechnicalPlan()
window.yibiao.tasks.startBidAnalysis(payload)
window.yibiao.tasks.startOutlineGeneration(payload)
window.yibiao.tasks.startContentGeneration(payload)
window.yibiao.tasks.getActiveTasks()
window.yibiao.tasks.onTaskEvent(callback)
window.yibiao.export.exportWord(payload)
```

### IPC
`electron/ipc/*.cjs` 只注册通道，不写业务逻辑。

命名规则：
- 配置：`config:*`
- AI：`ai:*`
- 文件：`file:*`
- 工作区：`workspace:*`
- 后台任务：`tasks:*`
- 导出：`export:*`

### Services
`electron/services/*.cjs` 承载 Main 侧业务：
- `configStore.cjs`：配置读写，路径位于 Electron `userData`，包含文本模型、生图模型和文件解析配置。
- `aiService.cjs`：文本模型请求、生图模型测试、正文配图生图等 AI 能力封装。
- `fileService.cjs`：文档导入与解析，按配置选择本地或 MinerU 解析方式；新的文件上传/转换入口必须复用这里的 `parseDocumentWithConfig()`。
- `workspaceStore.cjs`：工作区业务数据缓存，例如技术方案流程状态和招标文件内容。
- `taskService.cjs`：后台长任务管理，负责招标文件解析、目录生成、正文生成等跨页面任务的状态推送和结果落盘。
- `exportService.cjs`：Word 导出，包含 Markdown 到 Word、图片插入、Mermaid 转图片和导出进度上报。
- `updateService.cjs`：打包应用启动后的 GitHub Release 更新检查、下载和重启安装提示。

Main 层规则：
- 文件读写显式使用 UTF-8。
- Windows 路径和中文路径要作为默认情况考虑。
- 不在 Renderer 暴露 `fs`、`path`、`ipcRenderer` 原始对象。
- Renderer 不决定文件解析方式；文件解析方式由 Main 从 `configStore` 读取配置后选择。
- 新增任何文件上传、导入、转换功能时，不要直接调用 `doc2markdown/convert.mjs` 或自行拼接 MinerU 请求；统一在 Main 侧调用 `electron/services/fileService.cjs` 的 `parseDocumentWithConfig(app, filePath, config, { assetScope })`。
- `parseDocumentWithConfig()` 会统一处理本地解析、MinerU Agent、MinerU Accurate、MinerU 不支持格式自动切回本地解析、设置页“保留图片”和图片资产落盘。业务代码只负责选择文件、保存业务状态和传入稳定的 `assetScope`。
- `assetScope` 必须能对应到业务资源，例如技术方案使用 `technical-plan`，知识库文档使用 `knowledge-${documentId}`。后续新功能应使用 `<feature>-<resourceId>` 这类可定位前缀，便于删除时清理历史图片。

## 数据存储
- 配置数据保存到 Electron `userData/user_config.json`，由 `configStore.cjs` 管理。
- 工作区业务数据保存到 Electron `userData/workspace/`，由 `workspaceStore.cjs` 管理。
- 技术方案缓存保存到 `userData/workspace/technical_plan.json`，包含当前步骤、上传文件名、招标文件内容和后续流程中间结果。
- 正文生成图片保存到 `userData/workspace/generated-images/`，正文中通过 `yibiao-asset://generated-images/...` 引用，不把图片 base64 长期写入 `technical_plan.json`。
- 文件解析保留的原文图片保存到 `userData/workspace/imported-images/<assetScope>-<timestamp>-<random>/`，正文中通过 `yibiao-asset://imported-images/...` 引用，不把图片 base64 长期写入业务 JSON。
- 删除或重置某个业务资源时，必须调用 `electron/utils/importedImages.cjs` 的 `deleteImportedImageBatches(app, assetScopePrefix)` 清理对应历史图片目录，避免用户长期使用后磁盘被遗留图片占满。
- 解析失败时 `parseDocumentWithConfig()` 会清理本次新建图片批次；业务删除、清空、重新导入或删除父级目录时仍必须按 `assetScopePrefix` 主动清理旧批次。
- 设置页通用配置中的“开发者模式”开启后，AI 请求日志保存到 Electron `userData/logs/ai/`；每次 AI 请求对应一个 JSON 日志文件，包含完整请求参数、响应内容或错误信息。
- Renderer 通过 `window.yibiao.workspace` 读写工作区数据，不直接写文件，也不把招标文件 Markdown、正文草稿等大文本长期放进 `localStorage`。
- `localStorage` 只用于轻量 UI 偏好或临时状态，不作为业务数据的权威来源。

## 后台长任务
- 招标文件解析、目录生成、正文生成等耗时任务必须放在 Electron Main 侧执行，不在页面组件里承载完整任务编排。
- Renderer 只负责启动任务、订阅 `window.yibiao.tasks.onTaskEvent()`、展示 `technical_plan.json` 中的任务状态和结果。
- 后台任务运行过程中必须持续写入 `workspaceStore`，确保切换页面后任务不中断，切回来能恢复当前日志、进度和结果。
- 页面卸载不能作为任务取消信号；如需取消任务，应单独设计取消接口。

## 技术方案流程约定
- Step01 只负责上传招标文件并展示解析出的 Markdown，不做 AI 理解或目录生成。
- Step02/Step03/Step04 的长任务统一走 Electron Main 后台任务，不在 Renderer 中承载完整生成流程。
- Step04 正文生成页面采用“左侧目录树/任务树 + 右侧内容阅读区”的布局；目录树应能体现每个小节的生成状态。
- 正文只生成叶子小节内容，父级目录只作为结构分组。
- 正文内容以 `outlineData.outline[*].content` 为展示和导出的权威来源，不要另建一套正文存储源。
- Step04 正文生成先由 AI 输出章节编排决策，再生成正文；是否使用表格或配图由 AI 结合章节内容自行判断，代码不要按关键词硬编码配图规则。
- 正文配图只有在设置页生图模型状态为 `available` 时执行；生图失败不应影响已生成正文落盘。
- Mermaid 图以 Markdown `mermaid` 代码块保存；Renderer 负责前端本地渲染预览，Word 导出时由 Main 通过 mermaid.ink 转图片，并通过 `window.yibiao.export.onWordExportProgress()` 展示进度和失败提示。
- 目录重新生成、编辑、添加或删除后，必须清空正文生成缓存和旧 `content`，避免旧正文污染新目录和导出结果。
- Step04 的工具条使用 `导出 Word` 和 `继续扩写`，不要再显示“下一步”。

## AI 与 Prompt
AI 调用分三层：
- 页面或 Hook 调 feature service。
- Feature service 调 `shared/prompts` 生成 messages，再调 `shared/ai/aiClient`。
- `aiClient` 通过 `window.yibiao.ai` 调 Electron Main。

Prompt 文件约定：
- `analysisPrompts.ts`：招标文件分析。
- `outlinePrompts.ts`：目录生成、子目录生成、目录审核。
- `contentPrompts.ts`：章节正文生成。
- `expandPrompts.ts`：旧方案目录提取。
- `duplicatePrompts.ts`：标书查重。
- `rejectionPrompts.ts`：废标项检查。
- `jsonRepairPrompts.ts`：JSON 修复。

当前除 `jsonRepairPrompts.ts` 外，多数 Prompt builder 是占位实现，后续迁移时参考旧 `backend/app/utils/prompts/` 的职责，但不要直接依赖旧文件。
- AI 生成正文属于不可信模型输出，Markdown 渲染默认不要启用 `rehypeRaw`，避免 HTML 被渲染成真实 DOM。
- 只有明确需要展示解析文档中 HTML 的场景，才允许局部启用 raw HTML，并在代码评审中说明原因。

## UI 风格
- 配色使用易标官网/工作台体系：`#2174FD`、`#4098FC`、`#5B54D3`、`#f8fafd`。
- 保持高端、清爽、克制，不使用花哨动效。
- 组件优先清晰的信息层级和可读性。
- 前端基础组件优先使用 Radix UI 组件实现，若未引用可自行安装，再用全局 CSS 覆盖视觉风格。
- 新页面不要默认加占空间的大标题横幅；除非业务确实需要说明上下文，否则直接进入核心内容或使用轻量导航/状态栏。
- 新增页面优先延续现有卡片、胶囊 Tab、轻量状态栏、`FloatingToolbar` 等模式，不要另起一套视觉语言。
- 新增或调整页面时，先查找并复用现有布局、按钮、卡片、表单、状态栏和工具条等视觉模式；页面专用样式只补充必要结构和间距，不要为局部页面重写一整套颜色、圆角、阴影和字号体系。
- 如果某个 UI 模式后续可能跨页面复用，优先沉淀到全局样式或 `shared/ui`，避免每个 feature 各写一套相似实现。
- 技术方案流程页优先沿用 `command-bar + progress-card + workspace` 结构；左侧列表/树、右侧阅读器的交互模式保持一致。
- 页面不要为了底部工具条额外预留空白；`FloatingToolbar` 是覆盖层，必要内容应由内部滚动区域承载。
- 通用 UI 放 `shared/ui/`，业务专用 UI 放 feature 内。
- 全局布局组件放 `components/`。

## 全局消息提示
- 所有成功、失败、警告、普通消息提示统一使用 `shared/ui/ToastProvider` 提供的 Toast。
- Toast 位置固定在窗口右上角。
- 不使用 `alert`，不在页面里自建长期占位提示条。
- 页面内只保留与业务状态强相关的空状态、加载态和结果态。

## 悬浮工具条
通用组件位于 `src/shared/ui/FloatingToolbar.tsx`。

使用方式：
```tsx
<FloatingToolbar groups={groups} />
```

- 不提供 App 级默认工具条；需要流程控制或保存入口的页面自行渲染 `FloatingToolbar`，例如技术方案步骤工具条、设置页保存工具条、标书查重处理工具条。
- 页面主体不要重复放与工具条同语义的按钮，避免同一动作出现多个入口。
- 工具条前置拖动手柄，用户只能通过手柄拖动工具条；不要让普通按钮区域承担拖动，避免点击和拖拽冲突。
- 工具条应悬浮在内容上层，不要求页面底部 `padding-bottom` 或空白占位；被遮挡的长内容通过内部滚动查看。

## 布局与滚动
- 应用外层保持 `100vh` 高度并隐藏全局溢出，禁止依赖 `body` 产生页面级滚动条。
- `content-shell` 只负责占满主内容区和承载悬浮层，不作为长内容滚动容器。
- 页面根容器需要设置 `height: 100%`、`min-height: 0`，需要滚动时在页面内部容器上使用 `overflow: auto`。
- 技术方案这类工作台页面优先使用 `command-bar + workspace` 的网格结构，`workspace` 占据剩余高度，左右面板分别内部滚动。
- 设置页使用页面根容器内部滚动，保存工具条继续悬浮覆盖在上层。

## 打包发布与自动更新
- 客户端使用 `electron-builder` 打包，配置位于 `client/package.json` 的 `build` 字段。
- GitHub Actions 工作流位于 `.github/workflows/release.yml`，推送 `v*` tag 时触发，例如 `v0.1.1`；也支持手动执行 workflow 并输入已有 tag。
- Release notes 由 workflow 根据上一个 tag 到当前 tag 的 `git log` 显式生成，避免 GitHub 原生自动说明只显示 `Full Changelog`。
- Windows 在 `windows-latest` 构建，产物包含 NSIS 安装包和 zip；macOS 在 `macos-latest` 构建，产物包含 dmg 和 zip。
- Windows 图标来自 `assets/icon.ico`，必须允许 electron-builder 编辑 exe 资源；macOS 图标由 workflow 使用 `assets/icon_256.png` 临时生成 `assets/icon.icns` 后打包。
- 当前未接入代码签名，Windows 可能出现 SmartScreen 提示，macOS 可能被 Gatekeeper 拦截；正式分发前再补签名和 macOS notarization。
- 未签名 Windows 安装包首次通过资源管理器双击启动时，可能被 Defender/SmartScreen 扫描而表现为短暂无响应；命令行启动或等待扫描完成后通常会恢复。正式分发前应通过代码签名改善该体验。
- workflow 会从 tag 中同步客户端版本号，例如 tag `v0.1.1` 会临时执行 `npm version 0.1.1 --no-git-tag-version --allow-same-version` 后再打包。
- workflow 使用 `electron-builder --publish never` 只负责构建，再通过 `gh release upload` 显式上传安装包、zip/dmg、blockmap 和 `latest*.yml` 更新元数据。
- 自动更新由 `electron-updater` 负责，只在 `app.isPackaged` 的打包应用中启用；`npm run dev` 开发调试模式不会检查或下载更新。
- 打包应用启动后会自动检查一次更新，之后每 30 分钟轮询一次；自动检查失败保持静默，不打断用户。
- 若 GitHub Release 存在更高版本，应用会先在后台下载更新包；下载完成后通过右上角常驻 Toast 提示“安装并重启 / 稍后”。
- 用户点击“安装并重启”后才会安装更新；不会自动安装，也不会在应用退出时自动安装。
- 用户点击“稍后”或关闭自动更新 Toast 后，同一版本在本次运行期间不会再次自动提醒；设置页“关于”中的“检查更新”仍可手动触发检查和提示。
- 本地 Windows 打包验证可运行：
```powershell
cd client
npm run dist:win
```
- 本地 macOS 打包需在 macOS 环境运行：
```powershell
cd client
npm run dist:mac
```
- 发布新版本的基本流程：
```powershell
git tag v0.1.1
git push origin v0.1.1
```
- 如果某个 tag 的 Release 已经创建但缺少产物，可以在 GitHub Actions 页面手动运行 `Release Client`，输入该 tag 重新生成说明并上传产物。

## 开发约定
- 优先小步提交，不做无关重构。
- 不新增全局状态库，除非实际复杂度需要。
- 不在组件内硬编码大段 Prompt。
- 不在 Renderer 存储 API Key。
- 不直接复用旧 `frontend/` 或 `backend/` 的源码路径。
- 不为新上传功能另写一套文档解析链路；复用 `parseDocumentWithConfig()`，并在删除对应业务资源时复用 `deleteImportedImageBatches()` 清理导入图片资产。
- 命名保持清晰：页面 `*Page.tsx`，服务 `*Service.ts` 或 `*Workflow.ts`，Hook 以 `use` 开头。

## 验证标准
每次改动至少运行：
```powershell
npm run build
```

涉及依赖变更时额外运行：
```powershell
npm audit
```

涉及 Electron Main 或 preload 时，需要手动运行：
```powershell
npm run dev
```

检查窗口是否正常打开、控制台是否有错误、`window.yibiao` 是否存在。
