基于 Electron + Vue 3 的新一代跨平台 AI 对话桌面应用
一个功能强大的多提供商 AI 聊天客户端,支持 Google Gemini 和 OpenRouter,提供分支化对话、多模态交互、智能搜索等企业级功能
| 特性 | Starverse | 其他客户端 |
|---|---|---|
| 🌳 分支化对话 | ✅ 完整树形结构 | ❌ 线性对话 |
| 🔍 全文搜索 | ✅ SQLite FTS5 | |
| 🎨 多模态支持 | ✅ 图片+文本混合 | |
| 🤖 多提供商 | ✅ Gemini + OpenRouter | |
| 💾 数据存储 | ✅ SQLite + Web Worker | |
| ⚡ 性能优化 | ✅ 75% 提升 | - |
| 📁 项目管理 | ✅ 完整分类系统 | ❌ 无 |
| 🔐 隐私保护 | ✅ 本地存储 |
📸 点击查看功能截图(即将添加)
- 主界面
- 分支对话系统
- 高级模型选择器
- 项目管理
- 设置页面
# 1. 克隆项目
git clone https://github.com/GuXinghai/starverse.git
cd starverse
# 2. 安装依赖
npm install
# 3. 启动应用
npm run electron:dev- 多提供商支持: 支持 Google Gemini 和 OpenRouter 双提供商,可自由切换
- 统一模型管理 (AppModel): ⭐ v0.10 架构升级
- 统一的模型数据结构,消除 ModelData/ModelParameterSupport 等碎片化类型
- 自动能力检测(推理、工具调用、JSON 模式、多模态)
- 智能价格展示(USD / 1M tokens)
- 软删除机制(云端下架模型标记为 archived)
- 规范化字段映射(router_source、vendor、capabilities、pricing)
- OpenRouter 模型同步:
- 支持 200+ 模型(GPT-4, Claude, Gemini, Llama, DeepSeek R1 等)
- 自动从 /api/v1/models 同步最新列表
- 增量更新 + 软删除策略
- 本地 SQLite 缓存(避免重复请求)
- 首次出现时间 (first_seen_at) 和最后更新时间 (last_seen_at) 追踪
- Gemini 模型支持:
- gemini-pro, gemini-1.5-flash, gemini-2.0-flash-exp 等
- 自动规范化为 AppModel 结构
- 多模态支持:
- 上传图片到 AI 模型进行分析(GPT-4o、Gemini 1.5+、Claude 3)
- 接收 AI 生成的图片
- 🆕 一键切换图片比例(1:1、16:9、9:16 等)
- 支持消息编辑时添加/移除图片
- 图片点击使用系统默认应用打开
- 统一处理多种格式(URL、base64、data URI、inline_data)
- 网络搜索集成:
- OpenRouter Web 搜索插件支持
- 三档搜索深度:快速、普通、深入
- 灵活引擎配置:native、exa 或自动选择
- 每条消息可独立启用/禁用
- 推理功能(Reasoning) ⭐ 新增:
- 支持 OpenRouter Reasoning API
- 四个推理级别:minimal、low、medium、high
- 可视化推理过程显示(支持的模型)
- 智能成本预估和延迟提示
- 自动模型能力检测
- 使用统计分析 ⭐ 新增:
- 完整的 OpenRouter 使用数据追踪
- 按模型、时间范围分析成本和令牌使用
- 使用趋势图表和统计卡片
- 可视化仪表板
- 数据持久化到 SQLite
- 流式响应: 实时流式输出 AI 回复
- 上下文管理: 完整的对话历史管理
- 用量可视化:
- 实时解析 OpenRouter usage chunk
- 显示 Token 统计和 Credits 费用
- 数据持久化到分支元数据
- Markdown 支持: GitHub Flavored Markdown (GFM)
- 标题、列表、表格、引用、链接、图片
- 加粗、斜体、删除线
- 代码高亮: highlight.js 语法高亮
- 支持 200+ 编程语言
- GitHub 浅色主题
- LaTeX 数学公式: KaTeX 公式渲染
- 行内公式:
$E = mc^2$ - 块级公式:
$$\int_0^1 x^2 dx$$
- 行内公式:
- 智能渲染:
- 流式传输时显示纯文本
- 完成后渲染 Markdown
- 无语言代码块作为 Markdown 渲染
- 多会话: 创建和管理无限数量的对话
- 智能复用: 创建新会话时智能复用空白聊天
- 只复用默认名称("新对话")的空白会话
- 保护已重命名但未开始的会话
- 项目管理:
- 创建项目对对话进行分类管理
- 项目重命名和删除
- 对话快速分配到项目
- 项目筛选视图(全部/未分配/指定项目)
- 右键菜单快速移动对话
- 标签页模式: 类似浏览器的多标签界面
- 会话持久化: electron-store 自动保存
- 会话操作: 重命名、删除、清空消息
- 树形对话历史: 任意消息处创建新分支
- 多版本回复: 同一位置的多个 AI 回复可自由切换
- 路径导航: 清晰展示当前路径和分支结构
- 分支管理: 删除单个版本或整个分支树
- 多模态分支: 完全支持文本和图像混合消息
- 收藏模型: 快速访问常用 AI 模型
- 智能滚动动画:模型名称过长时自动滚动
- 四阶段动画:停顿 → 阅读 → 压缩 → 循环
- 自适应窗口大小变化
- 智能筛选:
- 模型系列筛选(GPT、Claude、Gemini、Llama)
- 多模态能力筛选(文本、图像、音频)
- 上下文长度筛选(自适应分位数滑块)
- 价格区间筛选
- 实时搜索: 模糊搜索模型名称、ID 和描述
- 多维排序: 按名称、上下文长度或价格排序
- 详细信息: 显示模型描述、定价、上下文长度等
- 编辑用户消息: 双击或点击编辑按钮修改已发送消息
- 重新生成: 编辑后自动触发 AI 重新生成回复
- 图片管理: 编辑时可添加或移除图片附件
- 历史保留: 编辑操作创建新分支,保留原有路径
- 智能去重: 编辑未改动内容时不生成冗余版本
- 边界处理: 无回复的提问也可触发首次生成
- SQLite 存储:
- better-sqlite3 高性能数据库引擎
- Web Worker 异步操作,不阻塞 UI
- 项目管理独立存储(完整 CRUD 操作)
- 对话归档功能(archive/restore)
- FTS5 全文搜索支持(中英文分词)
- 外键约束确保数据一致性
- 完整的数据迁移系统
- 边界防御机制:
- IPC 边界统一去代理化(深度去除 Vue Proxy)
- 消除 Vue Proxy structuredClone 错误
- Web Worker 异步数据库操作
- 增量序列化优化大型对话保存
- 数据验证 Schema(Zod)
- SQLite FTS5 全文检索:
- 支持对话标题和消息内容搜索
- 中英文分词和停用词过滤
- 按相关性排序搜索结果
- 实时索引更新
- 高性能查询(< 10ms)
- 自定义指令: 为每个对话设置专属的系统提示词
- 持久化保存,无需每次输入
- 统一生成参数架构 ⭐ 新增:
- 标准化所有 AI 参数处理(采样、推理、长度控制)
- 4 层配置覆盖系统(默认值 → 全局 → 对话 → 消息级)
- 模型能力自动检测系统
- 智能参数验证和过滤
- 现代化聊天输入 ⭐ 新增:
- 胶囊式浮动设计,类似 Perplexity/ChatGPT
- 集成工具栏按钮(附件、搜索、推理、图像生成、参数)
- 自适应多行输入(1-10 行自动扩展)
- 智能发送按钮状态切换(发送/停止/撤回)
- 完整的附件预览系统
- 不占用消息历史,不影响上下文长度
- 支持角色扮演、格式控制、专业领域限定等场景
- 兼容 Gemini 和 OpenRouter
- 采样参数配置: 精细控制 AI 生成行为
- 温度(Temperature):0-2,控制随机性
- Top-P/Top-K:核采样和词汇截断
- 频率/存在惩罚:减少重复,鼓励新话题
- 最大输出长度:限制回复长度
- 随机种子:实现可重现输出
- 每个对话独立配置,持久化保存
- 思维链推理支持:
- 支持 OpenRouter 推理模型(o1、Gemini Thinking 等)
- 可配置推理可见性(显示/隐藏)
- 推理努力程度控制(低/中/高)
- 最大推理 Token 限制
- 现代化 UI: 基于 Tailwind CSS 的精美界面设计
- 🆕 现代化工具栏: Plus Menu + Chips 交互模型
- ✅ 紧凑型按钮设计(文件上传、绘画、推理、搜索、参数)
- ✅ 内联配置菜单(搜索深度、推理努力程度)
- ✅ 一键比例切换(图像生成场景)
- ✅ 智能禁用状态(根据模型能力自动调整)
- ✅ Smart Parent, Dumb Child 架构(纯展示组件)
- 响应式布局: 自适应不同窗口大小
- 🎯 智能菜单定位 ⭐ 最新:
- ✅ 主菜单和子菜单独立 Teleport 到 body
- ✅ 智能防溢出算法(优先级: 右→下/上→左)
- ✅ 响应窗口大小、滚动、DPI 变化
- ✅ 防止被容器的 overflow 裁剪
- ✅ 正确的 z-index 层级管理
- ⚡ 性能优化 ⭐ 最新:
- ✅ 标签页切换性能提升 75%(40-50ms → 10ms)
- ✅ 持久化优化:UI 状态保存速度提升 40 倍(0.8ms → 0.02ms)
- ✅ 智能增量保存策略(标签状态 vs 完整数据)
- ✅ O(1) 对话查找性能(conversationsMap)
- ✅ 条件化昂贵计算(减少多实例重复计算)
- 加载动画: 优雅的应用初始化和消息加载动画
- 草稿保存: 自动保存输入框的草稿内容
- 鼠标悬停预览: 消息悬停显示编辑和分支控制按钮
- 滚动优化: 支持拖动滚动条时暂停自动滚动
- 多提供商配置: 可视化选择 Gemini 或 OpenRouter
- API Key 管理: 安全存储和配置多个 API Key
- 模型切换: 每个会话可独立选择 AI 模型
- 设置界面: 友好的设置面板,集中管理所有配置
- 完善的代码注释:
- ChatView.vue: 800+ 行详细注释(26% 覆盖率)
- 架构说明、算法解释、性能考量文档化
- 所有活跃组件通过注释质量审查
- 代码质量保障:
- TypeScript 严格模式,零编译错误
- 移除非关键调试日志,保留错误追踪
- 模块化架构,职责清晰
- 边界防御实施(IPC 层)
- 完善的文档体系:
- 详细的架构文档和开发指南
- API 使用文档和最佳实践
- 问题修复记录和优化总结
- 70+ 份技术文档
- 持久化架构:
- SQLite 数据库(对话、消息、项目)
- better-sqlite3 + Web Worker(异步操作)
- FTS5 全文搜索(中英文分词)
- 增量序列化优化
- 完整的数据迁移系统
- 安全性保障:
- Electron 上下文隔离
- 预加载脚本安全桥接
- API Key 加密存储
- structuredClone 边界防御
- DOMPurify HTML 清理
- 性能优化:
- 标签页切换 75% 性能提升(10ms)
- UI 状态保存速度提升 40 倍
- 智能增量保存策略
- O(1) 对话查找性能
- 虚拟滚动和懒加载
| 技术 | 版本 | 用途 |
|---|---|---|
| Electron | 38.6.0 | 跨平台桌面应用框架 |
| Vue.js | 3.4.21 | 渐进式前端框架(Composition API) |
| TypeScript | 5.2.2 | 类型安全的 JavaScript 超集 |
| Vite | 5.1.6 | 新一代前端构建工具 |
| Pinia | 3.0.3 | Vue 3 官方状态管理库(11个模块化 Store) ⭐ |
| 技术 | 版本 | 用途 |
|---|---|---|
| better-sqlite3 | 12.4.1 | 高性能同步 SQLite 数据库 |
| electron-store | 11.0.2 | Electron 配置数据持久化 |
| Zod | 3.23.8 | TypeScript 优先的 Schema 验证 |
| 技术 | 版本 | 用途 |
|---|---|---|
| Tailwind CSS | 4.1.16 ⭐ | 原子化 CSS 框架(v4 CSS 优先配置) |
| @tailwindcss/postcss | 4.1.16 ⭐ | Tailwind v4 PostCSS 引擎 |
| marked | 16.4.1 | Markdown 解析和渲染 |
| KaTeX | 0.16.25 | LaTeX 数学公式渲染 |
| highlight.js | 11.11.1 | 代码语法高亮 |
| DOMPurify | 3.3.0 | HTML 安全过滤 |
| 技术 | 版本 | 用途 |
|---|---|---|
| @google/generative-ai | 0.24.1 | Google Gemini AI SDK |
| Fetch API | - | OpenRouter API 调用(SSE) |
| 技术 | 版本 | 用途 |
|---|---|---|
| uuid | 13.0.0 | 唯一标识符生成 |
| @vueuse/core | 14.0.0 | Vue Composition 工具集 |
- electron-vite: Electron 项目的 Vite 集成
- electron-builder: 多平台应用打包工具
- vue-tsc: Vue 3 TypeScript 类型检查
- concurrently: 并行运行多个命令
- esbuild: 快速 JavaScript 打包器
Starverse/
├── electron/ # Electron 主进程
│ ├── main.ts # 应用主入口(窗口、生命周期)
│ ├── preload.ts # 预加载脚本(IPC 安全桥接)
│ ├── db/ # 数据库 Worker 管理
│ │ ├── workerManager.ts # Worker 进程管理器
│ │ └── worker.ts # SQLite Worker 实现
│ └── ipc/ # IPC 处理器
│ └── dbBridge.ts # 数据库 IPC 桥接
│
├── src/ # Vue 渲染进程
│ ├── components/ # 12 个 Vue 组件
│ │ ├── ChatView.vue # 核心聊天视图(5000+ 行)
│ │ ├── TabbedChatView.vue # 多标签容器
│ │ ├── ConversationList.vue # 对话列表侧边栏
│ │ ├── AdvancedModelPickerModal.vue # 高级模型选择器
│ │ ├── FavoriteModelSelector.vue # 收藏模型选择器
│ │ └── ... # 其他组件
│ │
│ ├── services/ # 业务逻辑服务
│ │ ├── aiChatService.js # AI 服务路由器(策略模式)
│ │ ├── chatPersistence.ts # SQLite 对话持久化
│ │ ├── projectPersistence.ts # 项目管理持久化
│ │ ├── searchService.ts # FTS5 全文搜索
│ │ ├── db/ # 数据库 IPC 封装
│ │ │ ├── index.ts # 数据库服务客户端
│ │ │ └── types.ts # 类型定义
│ │ └── providers/ # AI 提供商实现
│ │ ├── GeminiService.js # Google Gemini
│ │ ├── GeminiService.ts # Google Gemini (TypeScript) ⭐
│ │ ├── OpenRouterService.ts # OpenRouter (TypeScript) ⭐
│ │ ├── generationAdapter.ts # 统一生成参数适配器 ⭐
│ │ ├── openrouterReasoningAdapter.ts # 推理参数适配器 ⭐
│ │ └── modelCapability.ts # 模型能力检测 ⭐
│ │
│ ├── stores/ # Pinia 状态管理(模块化架构)
│ │ ├── index.ts # appStore(全局配置,249 行)
│ │ ├── conversation.ts # 对话管理(433 行)
│ │ ├── branch.ts # 分支树操作(422 行)
│ │ ├── model.ts # 模型管理(265 行)
│ │ ├── persistence.ts # 持久化调度(271 行)
│ │ ├── project.ts # 项目管理(475 行)
│ │ ├── projectWorkspace.ts # 项目工作区状态
│ │ ├── analyticsStore.ts # 使用统计状态 ⭐ 新增
│ │ ├── usageStore.ts # 使用数据管理 ⭐ 新增
│ │ ├── dashboardPrefs.ts # 仪表板配置 ⭐ 新增
│ │ └── branchTreeHelpers.ts # 分支树算法(520 行)
│ │ ├── projectWorkspaceStore.ts # 项目工作区(376 行)
│ │ └── branchTreeHelpers.ts # 分支树算法(1140 行)
│ │
│ ├── types/ # TypeScript 类型
│ │ ├── chat.ts # 聊天类型(300+ 行)
│ │ └── electron.d.ts # Window 接口扩展
│ │
│ ├── utils/ # 工具函数
│ │ ├── electronBridge.ts # Electron API 桥接
│ │ └── ipcSanitizer.js # IPC 数据清理
│ │
│ ├── App.vue # 根组件
│ └── main.ts # Vue 应用入口
│
├── infra/ # 基础设施层(TypeScript)
│ └── db/ # 数据库核心
│ ├── schema.sql # SQLite Schema 定义
│ ├── worker.ts # Worker 实现
│ ├── types.ts # 数据库类型
│ ├── validation.ts # Zod Schema 验证
│ └── repo/ # Repository 层(数据访问)
│ ├── convoRepo.ts # 对话仓储
│ ├── messageRepo.ts # 消息仓储
│ ├── projectRepo.ts # 项目仓储
│ └── searchRepo.ts # 搜索仓储
│
├── public/ # 静态资源
├── docs/ # 70+ 份技术文档
├── archived-components/ # 归档组件
└── dist/ # 构建输出
| 模块 | 文件数 | 代码行数 | 说明 |
|---|---|---|---|
| 核心组件 | 13+ | ~12,200 | Vue 3 Composition API |
| - ChatView.vue | 1 | 5,312 | 聊天核心逻辑、多实例架构 |
| - ConversationList.vue | 1 | 1,474 | 项目树 + 对话列表(🚧 重构中,TODO 1.1-1.3 已完成) |
| - ChatToolbar.vue | 1 | ~450 | 🆕 现代化工具栏(Plus Menu + Chips) |
| - AdvancedModelPickerModal.vue | 1 | 1,353 | 高级模型选择器 |
| - ProjectHome.vue | 1 | 1,171 | 项目主页 |
| 状态管理 | 8 | ~3,800 | Pinia Stores (模块化) |
| - conversation.ts | 1 | 433 | 对话管理、标签页 |
| - branch.ts | 1 | 422 | 分支树操作 |
| - branchTreeHelpers.ts | 1 | 1,140 | 树形算法实现 |
| - model.ts | 1 | ~380 | 🆕 模型管理 + 能力系统 Phase 2(ModelGenerationCapability) |
| Composables | 13+ | ~1,800 | Vue 组合式函数 |
| - 🆕 useConversationSearch.ts | 1 | ~300 | TODO 1.3: 对话搜索逻辑(FTS5 + DSL) |
| - 🆕 useMenuPositioning.ts | 1 | ~150 | TODO 1.2: 菜单智能定位算法 |
| - 🆕 useFormatters.ts | 1 | ~100 | TODO 1.1: 格式化工具函数集合 |
| - model.ts | 1 | 265 | 模型管理 |
| - persistence.ts | 1 | 271 | 持久化调度 |
| - project.ts | 1 | 475 | 项目管理 |
| 服务层 | 8 | ~3,000 | 业务逻辑层 |
| - OpenRouterService.js | 1 | 1,337 | SSE 流式解析 |
| - chatPersistence.ts | 1 | ~400 | SQLite 持久化 |
| 类型定义 | 2 | ~400 | TypeScript 类型 |
| - chat.ts | 1 | 312 | 多模态消息类型 |
| 基础设施 | 6 | ~800 | 数据库层 |
| - schema.sql | 1 | ~150 | DDL 定义 |
| - repo/* | 4 | ~500 | 数据访问对象 |
| 文档 | 70+ | ~50,000 | Markdown 文档 |
| 总计 | ~100 | ~18,000+ | 不含 node_modules |
┌─────────────────────────────────────────────────────────────┐
│ Electron 主进程 (main.ts) │
│ - 窗口管理(BrowserWindow) │
│ - 应用生命周期(app events) │
│ - IPC 通信处理(ipcMain.handle) │
│ - DB Worker 管理(DbWorkerManager) │
└────────────────────────┬────────────────────────────────────┘
│ contextBridge (preload.ts)
│ 安全的 IPC 桥接
┌────────────────────────┴────────────────────────────────────┐
│ Vue 渲染进程 (src/) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 表现层 (Presentation Layer) │ │
│ │ 12 个 Vue 组件,采用 Composition API │ │
│ │ - ChatView.vue (5000+ 行核心视图) │ │
│ │ - TabbedChatView.vue (多实例容器) │ │
│ │ - ConversationList.vue (项目+对话树) │ │
│ │ - AdvancedModelPickerModal.vue (高级选择器) │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴─────────────────────────────────┐ │
│ │ 状态层 (State Management - Pinia,模块化设计) │ │
│ │ - appStore (249 行): 全局配置、API Key、Provider │ │
│ │ - conversationStore (433 行): 对话 CRUD、标签管理 │ │
│ │ - branchStore (422 行): 分支树操作、流式追加 │ │
│ │ - modelStore (265 行): 模型列表、收藏管理 │ │
│ │ - persistenceStore (271 行): 自动保存、脏数据追踪 │ │
│ │ - projectStore (475 行): 项目管理、对话关联 │ │
│ │ - branchTreeHelpers (1140 行): 树算法、序列化 │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴─────────────────────────────────┐ │
│ │ 服务层 (Service Layer) │ │
│ │ - aiChatService: AI 服务路由器(策略模式) │ │
│ │ ├─ GeminiService: Google Gemini SDK │ │
│ │ └─ OpenRouterService: SSE 流式(1300+ 行) │ │
│ │ - chatPersistence: SQLite 持久化服务 │ │
│ │ - projectPersistence: 项目管理服务 │ │
│ │ - searchService: FTS5 搜索 DSL │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ IPC: db:invoke │
└───────────────────────┼──────────────────────────────────────┘
│
┌───────────────────────┴──────────────────────────────────────┐
│ 基础设施层 (Infrastructure - infra/db) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Web Worker (异步 SQLite 操作) │ │
│ │ - workerManager.ts: 任务调度、超时控制 │ │
│ │ - worker.ts: SQLite3 + Repository 实例化 │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴─────────────────────────────────┐ │
│ │ Repository 层 (数据访问对象) │ │
│ │ - ConvoRepo: 对话 CRUD + 归档 │ │
│ │ - MessageRepo: 消息批量操作 + FTS5 索引 │ │
│ │ - ProjectRepo: 项目管理 + 计数统计 │ │
│ │ - SearchRepo: FTS5 全文检索 + BM25 排序 │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴─────────────────────────────────┐ │
│ │ SQLite 数据库 (chat.db) │ │
│ │ - WAL 模式,外键约束 │ │
│ │ - 7 张表 + 1 个 FTS5 虚拟表 │ │
│ │ - schema.sql: 完整 DDL 定义 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
appStore (全局应用状态)
- 多提供商配置 (
activeProvider,geminiApiKey,openRouterApiKey) - API Key 管理和持久化
- 应用初始化状态 (
isAppReady) - 全局配置管理
chatStore (对话状态)
- 对话会话管理(
conversations) - 标签页状态(
openConversationIds,activeTabId) - 模型列表(
availableModels) - 提供原子化 API,支持异步安全操作
┌──────────────────────────────────┐
│ aiChatService (路由器) │
│ - getProviderContext() │
│ - listAvailableModels() │
│ - streamChatResponse() │
└──────────────┬───────────────────┘
│
根据 activeProvider 路由
│
┌──────────┴──────────┐
│ │
┌───▼──────────┐ ┌──────▼──────────┐
│GeminiService │ │OpenRouterService│
│- Google SDK │ │- Fetch + SSE │
└──────────────┘ └─────────────────┘
-- 核心表结构
CREATE TABLE project (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
meta TEXT -- JSON 元数据
);
CREATE TABLE convo (
id TEXT PRIMARY KEY,
project_id TEXT REFERENCES project(id) ON DELETE SET NULL,
title TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
meta TEXT -- JSON: 包含分支树、模型、草稿等
);
CREATE TABLE message (
id TEXT PRIMARY KEY,
convo_id TEXT NOT NULL REFERENCES convo(id) ON DELETE CASCADE,
role TEXT NOT NULL, -- 'user' | 'assistant'
created_at INTEGER NOT NULL,
seq INTEGER NOT NULL, -- 消息序号
meta TEXT, -- JSON: branchId, versionId, metadata
UNIQUE (convo_id, seq)
);
CREATE TABLE message_body (
message_id TEXT PRIMARY KEY REFERENCES message(id) ON DELETE CASCADE,
body TEXT NOT NULL -- 消息正文
);
CREATE TABLE tag (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE convo_tag (
convo_id TEXT NOT NULL REFERENCES convo(id) ON DELETE CASCADE,
tag_id TEXT NOT NULL REFERENCES tag(id) ON DELETE CASCADE,
PRIMARY KEY (convo_id, tag_id)
);
-- FTS5 全文搜索虚拟表
CREATE VIRTUAL TABLE message_fts USING fts5(
message_id UNINDEXED,
convo_id UNINDEXED,
body,
tokenize = 'unicode61' -- 中英文分词
);
-- 性能优化索引
CREATE INDEX idx_convo_project ON convo(project_id);
CREATE INDEX idx_convo_updated ON convo(updated_at DESC);
CREATE INDEX idx_msg_convo_seq ON message(convo_id, seq);
-- WAL 模式配置
PRAGMA journal_mode=WAL; -- Write-Ahead Logging
PRAGMA synchronous=NORMAL; -- 性能与安全平衡
PRAGMA foreign_keys=ON; -- 外键约束
PRAGMA mmap_size=268435456; -- 256MB 内存映射
PRAGMA cache_size=-20000; -- 20MB 缓存分支树存储策略:
convo.meta存储完整分支树的序列化 JSONmessage表仅存储当前路径的消息(用于 FTS5 搜索)- 分支切换时重建
message表记录,保持搜索索引同步
// 预加载脚本暴露的安全 API (preload.ts)
contextBridge.exposeInMainWorld('electronStore', {
get: (key: string) => ipcRenderer.invoke('store-get', key),
set: (key: string, value: any) => ipcRenderer.invoke('store-set', key, value),
delete: (key: string) => ipcRenderer.invoke('store-delete', key)
})
contextBridge.exposeInMainWorld('electronDb', {
invoke: (payload: { method: DbMethod, params?: unknown }) =>
ipcRenderer.invoke('db:invoke', payload)
})
contextBridge.exposeInMainWorld('electronApi', {
openExternal: (url: string) => ipcRenderer.invoke('api:open-external', url),
saveTempImage: (dataUrl: string) => ipcRenderer.invoke('api:save-temp-image', dataUrl)
})IPC 安全机制:
- Context Isolation: 完全隔离主进程和渲染进程
- 白名单模式: 只暴露必要的 API 方法
- 数据清理:
ipcSanitizer深度去除 Vue Proxy - 方法验证: 数据库 IPC 只允许预定义的 26 个方法
┌──────────────────────────────────────────────────────────┐
│ 用户输入消息 │
└────────────────────────┬─────────────────────────────────┘
↓
┌────────────────────────┴─────────────────────────────────┐
│ ChatView.vue - handleSendMessage() │
│ - 捕获 conversationId(上下文固化) │
│ - 上传图片 → base64 data URI │
│ - 调用 branchStore.addBranch() │
└────────────────────────┬─────────────────────────────────┘
↓
┌────────────────────────┴─────────────────────────────────┐
│ BranchStore + ConversationStore │
│ 1. 添加用户消息到分支树(branchStore) │
│ 2. 创建 AI 消息占位符(branchStore) │
│ 3. 调用 aiChatService.streamChatResponse() │
│ 4. 标记脏数据(persistenceStore) │
└────────────────────────┬─────────────────────────────────┘
↓
┌────────────────────────┴─────────────────────────────────┐
│ aiChatService.js - 策略模式路由 │
│ 根据 appStore.activeProvider 选择服务: │
│ - Gemini → GeminiService │
│ - OpenRouter → OpenRouterService │
└─────────────┬───────────────────────┬────────────────────┘
↓ ↓
┌───────────────────┐ ┌──────────────────────┐
│ GeminiService.js │ │ OpenRouterService.js │
│ Google SDK │ │ Fetch + SSE Parser │
│ generateContent() │ │ 1300+ 行实现 │
└───────────────────┘ └──────────────────────┘
↓ ↓
┌───────────────────┐ ┌──────────────────────┐
│ Gemini API │ │ OpenRouter API │
│ 流式响应 │ │ SSE 流式响应 │
└───────────────────┘ └──────────────────────┘
↓ ↓
└───────────┬───────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ chatStore - 逐 Token 更新 │
│ - appendTokenToMessage() / appendImageToMessage() │
│ - 更新分支树中的消息内容 │
│ - Vue 响应式自动触发 UI 更新 │
└─────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ ContentRenderer.vue - 实时渲染 │
│ - 流式中:显示纯文本(性能优化) │
│ - 完成后:Markdown + LaTeX + 代码高亮 │
└─────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ chatStore - 自动持久化(50ms 防抖) │
│ 调用 sqliteChatPersistence.saveConversation() │
└─────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ chatPersistence.ts │
│ 1. deepToRaw() 去除 Vue Proxy │
│ 2. serializeTree() 分支树序列化 │
│ 3. toMessageSnapshots() 提取当前路径消息 │
│ 4. 调用 dbService.saveConvo() / replaceMessages() │
└─────────────────────────┬────────────────────────────────┘
↓ IPC: db:invoke
┌─────────────────────────┴────────────────────────────────┐
│ 主进程 - dbBridge.ts │
│ 验证方法白名单 → 调用 DbWorkerManager │
└─────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ Web Worker - worker.ts │
│ 1. 接收任务(通过 postMessage) │
│ 2. 实例化 Repository │
│ 3. 执行 SQLite 操作(同步调用,不阻塞 UI) │
│ 4. 返回结果 │
└─────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────┴────────────────────────────────┐
│ SQLite 数据库 (chat.db) │
│ - convo 表:存储分支树 JSON │
│ - message + message_fts:当前路径 + FTS5 索引 │
│ - 事务保证数据一致性 │
└──────────────────────────────────────────────────────────┘
确保您的开发环境满足以下要求:
| 工具 | 版本要求 | 检查命令 |
|---|---|---|
| Node.js | >= 18.0.0 | node --version |
| npm | >= 9.0.0 | npm --version |
| 操作系统 | Windows 10+, macOS 10.13+, Ubuntu 18.04+ | - |
步骤 1: 克隆项目
git clone https://github.com/GuXinghai/starverse.git
cd starverse步骤 2: 安装依赖
npm install
⚠️ Windows 用户注意: 如果遇到better-sqlite3编译错误,请运行npm run rebuild
步骤 3: 配置 API Key
启动应用后,点击右上角设置图标 ⚙️,选择您的 AI 提供商:
选项 A: Google Gemini(免费配额)
- 访问 Google AI Studio
- 登录您的 Google 账号
- 点击 "Create API Key"
- 复制 API Key
- 在 Starverse 设置中:
- 选择 "Google Gemini"
- 粘贴 API Key
- 点击保存
优势:
- 免费配额充足
- 官方 SDK 支持
- 低延迟
选项 B: OpenRouter(访问上百种模型)
- 访问 OpenRouter
- 注册账号(支持 Google/GitHub 登录)
- 创建 API Key
- 充值少量余额(最低 $5)
- 在 Starverse 设置中:
- 选择 "OpenRouter"
- 粘贴 API Key
- 点击保存
优势:
- 访问 GPT-4, Claude, Gemini, Llama 等 100+ 模型
- 按需付费,价格透明
- 支持网络搜索插件
步骤 4: 启动应用
npm run electron:dev✅ 应用将自动启动,Vite 开发服务器运行在 http://localhost:5173
- 创建第一个对话: 点击左侧边栏 "新对话" 按钮
- 选择 AI 模型: 点击顶部模型选择器,选择您想要的模型
- 开始聊天: 在底部输入框输入消息,按 Enter 发送
- 探索高级功能:
- 上传图片进行多模态对话
- 创建项目管理对话分类
- 使用分支功能探索不同对话路径
- 编辑消息重新生成回复
| 脚本 | 说明 |
|---|---|
npm run dev |
启动 Vite 开发服务器(仅渲染进程) |
npm run electron:dev |
启动完整 Electron 应用(开发模式,推荐) |
npm run build |
构建生产版本并打包 |
npm run preview |
预览构建结果 |
npm run dev:clean |
清理进程并重新启动开发服务器 |
npm run rebuild |
重新编译 better-sqlite3 原生模块 |
npm run build:worker |
构建数据库 Web Worker |
npm run watch:worker |
监听模式构建 Web Worker |
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useChatStore } from '@/stores/chatStore'
// 使用 Composition API
const chatStore = useChatStore()
const message = ref('')
// 计算属性
const isValid = computed(() => message.value.trim().length > 0)
</script>
<template>
<!-- 使用 Tailwind CSS 类名 -->
<div class="flex items-center p-4 bg-white rounded-lg shadow">
<input
v-model="message"
class="flex-1 px-4 py-2 border rounded"
placeholder="输入消息..."
/>
</div>
</template>import { useChatStore } from '@/stores/chatStore'
const chatStore = useChatStore()
// ✅ 推荐:使用带 conversationId 的异步安全 API
chatStore.addMessageToConversation(conversationId, {
role: 'user',
text: userInput
})
// ❌ 避免:依赖全局状态的旧 API(已弃用)
chatStore.addMessageToActiveConversation(message)- 优先使用 Tailwind 工具类
<!-- ✅ 推荐 -->
<div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
<!-- ❌ 避免自定义 CSS -->
<div class="custom-container"></div>- 响应式设计
<div class="w-full md:w-1/2 lg:w-1/3">
<!-- 移动端全宽,平板半宽,桌面端 1/3 宽 -->
</div>- 常用设计模式
<!-- 卡片 -->
<div class="bg-white rounded-lg shadow p-4">
<!-- 按钮 -->
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
<!-- 输入框 -->
<input class="w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500">- 渲染进程: Vue 组件、样式修改自动热重载
- 主进程: 修改
electron/main.ts需要重启应用 - 预加载脚本: 修改
electron/preload.ts需要刷新窗口 (Ctrl+R)
- 开发模式下自动打开 DevTools
- 使用 Vue DevTools 浏览器扩展
# 使用 VS Code 的 Node.js 调试器
# 或在主进程代码中使用 console.lognpm run build构建产物位于 release/ 目录
编辑 electron-builder.json5 自定义打包选项:
{
"appId": "com.yourcompany.starverse",
"productName": "Starverse",
"win": {
"target": ["nsis"],
"artifactName": "${productName}-Windows-${version}-Setup.${ext}"
},
"mac": {
"target": ["dmg"],
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
},
"linux": {
"target": ["AppImage"],
"artifactName": "${productName}-Linux-${version}.${ext}"
}
}# Windows
npm run build
# macOS (需要在 macOS 上运行)
npm run build
# Linux
npm run build如果遇到数据损坏或需要重置应用,可以使用以下工具完全清理所有聊天记录和项目数据:
Windows 批处理脚本 - 双击运行:
clear-all-data.batPowerShell 脚本 - 右键使用 PowerShell 运行:
.\clear-all-data.ps1独立清理脚本(推荐,无需 Electron 运行时):
node scripts/clear-all-data-standalone.cjsElectron 内清理脚本:
node scripts/clear-all-data.js清理工具会删除以下数据:
✅ SQLite 数据库文件
chat.db- 主数据库chat.db-wal- 预写日志chat.db-shm- 共享内存
✅ 对话和项目数据
- 所有聊天记录
- 所有项目和分类
- 打开的标签页记录
- 收藏的模型列表
🛡️ 保留的数据
- API 密钥配置
- 模型选择偏好
- 提供商设置
- 其他应用设置
- 已完全关闭 Starverse 应用
- 已备份重要的聊天记录(如需保留)
- 理解此操作不可逆
💡 清理后首次启动:
- 应用会自动创建新的数据库
- 创建一个新的空对话
- 所有项目列表为空
📖 详细说明:参见 数据清理指南
数据结构(branchTreeHelpers.ts):
type ConversationTree = {
branches: Map<string, Branch>, // branchId → Branch
rootBranchIds: string[], // 根分支 ID 列表
currentPath: string[] // 当前路径(branchId 序列)
}
type Branch = {
id: string,
parentBranchId: string | null,
versions: Version[], // 同一位置的多个版本
currentVersionIndex: number // 当前选中的版本
}
type Version = {
id: string,
message: Message, // 完整消息对象
children: string[] // 子分支 ID 列表
}核心算法:
- 添加分支: O(1) - Map 查找 + 数组追加
- 切换版本: O(n) - 重建当前路径,n 为深度
- 删除分支: O(m) - 递归删除子树,m 为子节点数
- 序列化: O(n) - 深度优先遍历,转换 Map 为数组
存储策略:
- 内存中使用
Map<string, Branch>保证 O(1) 查找 - 持久化时序列化为 JSON 数组存储在
convo.meta - 恢复时重建 Map 结构
Worker 管理器(electron/db/workerManager.ts):
class DbWorkerManager {
private worker: Worker | null = null
private requestMap: Map<number, PendingRequest> = new Map()
private requestId = 0
async call(method: DbMethod, params?: unknown): Promise<any> {
const id = ++this.requestId
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.requestMap.delete(id)
reject(new DbWorkerError('ERR_TIMEOUT', 'Request timeout'))
}, 30000) // 30秒超时
this.requestMap.set(id, { resolve, reject, timeout })
this.worker!.postMessage({ id, method, params })
})
}
}Worker 实现(infra/db/worker.ts):
// Worker 线程中的消息处理
self.onmessage = async (event: MessageEvent) => {
const { id, method, params } = event.data
try {
// 实例化 Repository
const repo = getRepository(method) // 根据 method 获取对应 repo
const result = await repo[methodName](params)
// 返回结果
self.postMessage({
id,
type: 'success',
result
})
} catch (error) {
self.postMessage({
id,
type: 'error',
error: serializeError(error)
})
}
}优势:
- ✅ SQLite 同步操作不阻塞 UI 线程
- ✅ 请求队列化,避免并发冲突
- ✅ 超时控制,防止请求挂起
- ✅ 错误隔离,Worker 崩溃不影响主进程
Schema 定义(infra/db/schema.sql):
-- FTS5 虚拟表
CREATE VIRTUAL TABLE message_fts USING fts5(
message_id UNINDEXED,
convo_id UNINDEXED,
body,
tokenize = 'unicode61' -- 支持中英文分词
);
-- 删除触发器(保持 FTS 索引同步)
CREATE TRIGGER trg_message_del AFTER DELETE ON message BEGIN
INSERT INTO message_fts(message_fts, rowid, message_id, convo_id, body)
VALUES('delete', (SELECT rowid FROM message_fts WHERE message_id = old.id), old.id, old.convo_id, '');
END;搜索实现(infra/db/repo/searchRepo.ts):
fulltext(params: FulltextQueryParams): FulltextResult[] {
const sql = `
SELECT
m.id AS messageId,
m.convo_id AS convoId,
snippet(message_fts, 2, '<em>', '</em>', '…', 20) as snippet,
bm25(message_fts) AS rank, -- BM25 相关性评分
m.created_at AS createdAt
FROM message_fts
JOIN message m ON m.id = message_fts.message_id
WHERE message_fts.body MATCH @query -- 全文匹配
ORDER BY rank, m.created_at DESC -- 按相关性排序
LIMIT @limit OFFSET @offset
`
return this.db.prepare(sql).all(params)
}搜索特性:
- ✅ BM25 算法排序(相关性评分)
- ✅ Snippet 高亮(20 字符上下文)
- ✅ 支持布尔查询(AND、OR、NOT)
- ✅ 项目和标签过滤
- ✅ 时间范围筛选
统一路由器(src/services/aiChatService.js):
export const aiChatService = {
getProviderContext(appStore) {
const provider = appStore.activeProvider
if (provider === 'Gemini') {
return {
service: GeminiService,
apiKey: appStore.geminiApiKey,
baseUrl: null
}
} else if (provider === 'OpenRouter') {
return {
service: OpenRouterService,
apiKey: appStore.openRouterApiKey,
baseUrl: appStore.openRouterBaseUrl
}
}
},
async* streamChatResponse(appStore, history, modelName, userMessage, options) {
const { service, apiKey, baseUrl } = this.getProviderContext(appStore)
// 统一的流式接口
yield* service.streamChatResponse(
apiKey,
history,
modelName,
userMessage,
baseUrl,
options.signal,
options
)
}
}OpenRouter SSE 解析(1300+ 行实现):
async* streamChatResponse(apiKey, history, modelName, userMessage, baseUrl, signal, options) {
const response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: modelName,
messages: formatMessages(history, userMessage),
stream: true,
// 采样参数
temperature: options.parameters?.temperature,
top_p: options.parameters?.top_p,
// 推理配置
reasoning: options.reasoning,
// Web 搜索
provider: { order: options.webSearch?.engines }
}),
signal
})
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim()
if (data === '[DONE]') return
const chunk = JSON.parse(data)
// 处理不同类型的 chunk
if (chunk.choices?.[0]?.delta?.content) {
yield { type: 'text', content: chunk.choices[0].delta.content }
}
if (chunk.usage) {
yield { type: 'usage', metrics: chunk.usage }
}
if (chunk.choices?.[0]?.delta?.reasoning_detail) {
yield { type: 'reasoning', detail: chunk.choices[0].delta.reasoning_detail }
}
}
}
}
}问题:Vue 3 的响应式 Proxy 无法通过 structuredClone 传递给 Electron IPC。
解决方案(src/services/chatPersistence.ts):
function deepToRaw(obj: any, depth: number = 0, path: string = 'root'): any {
// 处理原始类型
if (obj === null || obj === undefined || typeof obj !== 'object') {
return obj
}
// 去除顶层 Proxy
const raw = toRaw(obj)
// 递归处理数组
if (Array.isArray(raw)) {
return raw.map((item, index) =>
deepToRaw(item, depth + 1, `${path}[${index}]`)
)
}
// 递归处理对象
const result: any = {}
for (const key in raw) {
if (Object.prototype.hasOwnProperty.call(raw, key)) {
result[key] = deepToRaw(raw[key], depth + 1, `${path}.${key}`)
}
}
return result
}
// 保存对话前清理
async saveConversation(snapshot: ConversationSnapshot) {
// 1. 序列化分支树(Map → 数组)
const serializedTree = serializeTree(snapshot.tree)
// 2. 深度去除 Proxy
const cleanSnapshot = deepToRaw({ ...snapshot, tree: serializedTree })
// 3. 保存到 SQLite
await dbService.saveConvo({
id: cleanSnapshot.id,
meta: {
tree: cleanSnapshot.tree, // 已清理的树
model: cleanSnapshot.model,
// ...
}
})
}ChatView.vue 多实例设计:
<script setup lang="ts">
// ========== 上下文固化原则 ==========
// 问题:props.conversationId 在异步操作期间可能变化(标签切换)
// 解决:在异步任务启动时立即捕获到局部常量
const handleSendMessage = async () => {
const targetConversationId = props.conversationId // 🔒 固化
try {
// 异步操作使用固化的 ID,不受标签切换影响
await chatStore.sendMessage(targetConversationId, userInput, attachments)
} catch (error) {
console.error(`[ChatView] conversationId=${targetConversationId} send failed`)
}
}
// 流式响应也需要固化
watch(() => chatStore.streamingMessageId, () => {
const targetConversationId = props.conversationId // 🔒 固化
nextTick(() => {
if (chatStore.isConversationStreaming(targetConversationId)) {
scrollToBottom()
}
})
})
</script>TabbedChatView 容器管理:
<template>
<div v-for="id in chatStore.openConversationIds" :key="id">
<!-- 通过 display 控制可见性,不销毁实例 -->
<ChatView
:conversationId="id"
:style="{ display: id === chatStore.activeTabId ? 'flex' : 'none' }"
/>
</div>
</template>优势:
- ✅ 切换标签页不触发组件重建(性能优化)
- ✅ 后台标签的流式生成可以继续运行
- ✅ 保留滚动位置和输入框状态
- ✅ 避免多次重复加载模型列表
const conversationId = chatStore.createConversation()
chatStore.openConversationInTab(conversationId)await chatStore.sendMessage(conversationId, userInput)
// 自动处理:
// - 添加用户消息到历史
// - 调用 Gemini API
// - 流式接收并显示 AI 回复// 重命名会话
chatStore.renameConversation(conversationId, newTitle)
// 删除会话
chatStore.deleteConversation(conversationId)
// 清空消息
chatStore.clearConversationMessages(conversationId)// 自动根据 appStore.activeProvider 路由请求
import { aiChatService } from '@/services/aiChatService'
// 获取可用模型列表
const models = await aiChatService.listAvailableModels(appStore)
// 流式对话
const stream = aiChatService.streamChatResponse(
appStore,
history,
modelName,
userMessage,
abortSignal
)
for await (const chunk of stream) {
console.log(chunk) // 实时接收文本片段
}// GeminiService - 使用 Google 官方 SDK
import { GoogleGenerativeAI } from '@google/generative-ai'
// OpenRouterService - 使用 Fetch API + SSE 解析
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ model, messages, stream: true })
})export async function* streamChatResponse(apiKey, history, modelName, userMessage, signal) {
const genAI = new GoogleGenerativeAI(apiKey)
const model = genAI.getGenerativeModel({ model: modelName })
const result = await model.generateContentStream({
contents: [...history, { parts: [{ text: userMessage }] }],
signal: signal
})
for await (const chunk of result.stream) {
yield chunk.text()
}
}export async function* streamChatResponse(apiKey, history, modelName, userMessage, baseUrl, signal) {
const response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: modelName,
messages: history.map(msg => ({
role: msg.role === 'model' ? 'assistant' : msg.role,
content: msg.text
})),
stream: true
}),
signal: signal
})
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data:')) {
const jsonStr = line.slice(5).trim()
if (jsonStr === '[DONE]') return
const chunk = JSON.parse(jsonStr)
const content = chunk.choices?.[0]?.delta?.content
if (content) yield content
}
}
}
}// 保存对话数据
await window.electronStore.set('conversations', conversations.value)
// 加载对话数据
const savedConversations = await window.electronStore.get('conversations')// 自动获取所有支持 generateContent 的模型
const models = await geminiService.listAvailableModels(apiKey)
// 返回: ['models/gemini-pro', 'models/gemini-1.5-flash', ...]智能渲染 AI 回复中的 Markdown、LaTeX 和代码:
<template>
<ContentRenderer :content="message.text" />
</template>// 1. 提取 LaTeX 公式(避免被 Markdown 处理)
text = text.replace(/\$\$([\s\S]+?)\$\$/g, (match, formula) => {
const rendered = katex.renderToString(formula, { displayMode: true })
return placeholder // 使用占位符
})
// 2. Markdown 转 HTML
let html = marked(text, { renderer, breaks: true, gfm: true })
// 3. 替换占位符为渲染后的公式
html = html.replace(placeholder, renderedFormula)renderer.code = function({ text, lang }) {
// 无语言或 text/markdown/md → 作为 Markdown 渲染
if (!lang || ['text', 'markdown', 'md'].includes(lang)) {
return `<div class="nested-markdown">${marked(text)}</div>`
}
// 其他语言 → 语法高亮
const highlighted = hljs.highlight(text, { language: lang }).value
return `<pre><code class="hljs">${highlighted}</code></pre>`
}// 流式传输中:显示纯文本
<p v-if="isMessageStreaming(index)">{{ message.text }}</p>
// 流式完成后:完整渲染
<ContentRenderer v-else :content="message.text" />Markdown 基础语法
# 标题 1-6 级
**加粗** *斜体* ~~删除线~~
- 无序列表
1. 有序列表
> 引用块
[链接](url)
LaTeX 数学公式
行内公式:$E = mc^2$
块级公式:
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$代码块
```python
def hello():
print("Hello, World!")
```
```
无语言代码块会被当作 Markdown 渲染
### 这个标题会正常显示
```- Context Isolation: ✅ 已启用
// electron/main.ts
webPreferences: {
contextIsolation: true, // 隔离上下文
nodeIntegration: false, // 禁用 Node.js 集成
preload: path.join(__dirname, 'preload.mjs')
}- 预加载脚本白名单 API
// electron/preload.ts
contextBridge.exposeInMainWorld('electronStore', {
get: (key: string) => ipcRenderer.invoke('store-get', key),
set: (key: string, value: any) => ipcRenderer.invoke('store-set', key, value),
// 只暴露必要的 API
})- CSP (内容安全策略)
- 限制外部资源加载
- 防止 XSS 攻击
- API Key 安全
- 使用 electron-store 加密存储
- 多提供商独立管理,互不干扰
- 不在代码中硬编码
- 不暴露到渲染进程全局变量
- 密码输入框隐藏显示(支持切换)
- 格式验证(Gemini:
AIza..., OpenRouter:sk-or-v1-...)
- 网络安全
- SSE 缓冲区大小限制(10KB),防止恶意数据攻击
- AbortController 正确清理,防止内存泄漏
- HTTPS 加密传输
- 性能提升 75%: 从 40-50ms 降至 10ms
- 优化措施:
- 移除非关键 console.log(-15-25ms)
- 条件化昂贵计算(-10-15ms)
- O(1) conversationsMap 查找(-2-3ms)
- 用户体验: 流畅的 60fps 切换体验
- UI 状态保存速度提升 40 倍: 从 0.8ms 降至 0.02ms
- 智能增量保存策略:
saveTabState(): 仅保存标签状态(0.02ms,50ms 防抖)saveConversations(): 完整数据保存(0.8ms,按需触发)
- 减少 I/O 开销: 智能判断保存时机
- 虚拟滚动: 大量消息列表使用虚拟滚动
- 防抖输入: 草稿保存使用防抖避免频繁写入
- 条件渲染: 流式传输时显示纯文本,完成后渲染 Markdown
- Vue 3 响应式: 利用 Proxy 提升性能
- 流式处理: 不缓存完整响应,逐块处理
- AbortController 清理: 及时释放未完成的请求
- 组件卸载清理: 防止内存泄漏
- Web Worker 隔离: 数据库操作不阻塞主线程
- 并行加载配置: 使用
Promise.all()同时加载多个配置项 - 懒加载组件: 按需加载设置页面等非核心组件
- 启动速度: < 2 秒(优化后提升 60%)
// ChatTabs.vue - 标签切换性能监控
const startTime = performance.now()
// ... 切换逻辑
const duration = performance.now() - startTime
console.log(`标签切换耗时: ${duration.toFixed(2)}ms`)详细文档:PERFORMANCE_OPTIMIZATION_COMPLETE.md
| 错误类型 | 检测方式 | 提示信息 |
|---|---|---|
| API Key 未配置 | !apiKey |
"错误:未设置 {Provider} API Key,请先在设置页面配置。" |
| API Key 格式错误 | 正则验证 | " |
| 认证失败 | HTTP 401/403 | "{Provider} 认证失败:API Key 无效或已过期,请检查设置" |
| 速率限制 | HTTP 429 | "{Provider} 速率限制:请求过于频繁,请等待 X 秒后重试" |
| 网络错误 | Fetch 失败 | "网络连接失败,请检查网络设置" |
| 用户中止 | AbortError | "[已停止生成]" (静默处理) |
- 分支化聊天管理系统完整实现 🌳
- 高级模型选择器实现总结 🎯
- 分支树架构重构完成 🔧
- OpenRouter 接入重构总结 ⭐
- 系统默认应用打开图片 📷
- 网络搜索集成文档 🔍
- 采样参数功能说明 ⚙️
- SQLite 增强实施文档 🗄️
- 边界防御实施文档 🛡️
- Web Worker 实现文档 ⚡
- 增量序列化指南 📦
- 数据清理指南 🧹
- 性能优化完整总结 ⚡
- 标签切换性能优化实施 🏃
- ChatView 优化总结 📊
- 保存优化总结 💾
- 长对话性能优化 📈
- 自适应分位数滑块测试指南 🧪
- 分支树重构测试指南 ✅
- DOM 清理验证 🔍
- 存储验证报告 📋
- 2025年11月修复汇总 🔧
- 2025年1月更新汇总 ✨
- 全部修复完成总结 ✅
- 优先级修复总结 🎯
- 项目管理修复 📁
- 代码清理总结 🧹
- 代码清理报告 📊
- 组件归档说明 📦
- ChatView 注释改进 💬
- 收藏模型滚动动画实现 🎞️
- 智能菜单定位修复 🎯
- 按钮交互优化 🖱️
2025年1月 - 用户体验优化
- ✨ 收藏模型滚动动画
- 模型名称过长时自动启用智能滚动播放
- 四阶段环带算法:停顿(500ms)→ 阅读(50px/s)→ 压缩(4倍速)→ 循环
- 响应窗口大小变化,自动重新计算
- 完整的代码注释文档(689行新增)
- ✨ 新建会话复用优化
- 只复用默认名称("新对话")的空白会话
- 保护已重命名但未开始的会话,避免误覆盖
- 智能判断会话状态
详见:docs/RECENT_UPDATES_2025_01.md
2024年11月 - SQLite 迁移与性能优化 🗄️
- ✅ SQLite 数据库迁移
- 从 electron-store JSON 迁移到 SQLite
- better-sqlite3 + Web Worker 架构
- FTS5 全文搜索支持
- 完整的数据迁移系统
- ✅ 边界防御实施
- 深度去除 Vue Proxy 包装
- IPC 边界数据清理
- 消除 structuredClone 错误
- ✅ 性能优化
- 标签页切换性能提升 75%(40-50ms → 10ms)
- 持久化优化:UI 状态保存速度提升 40 倍
- 智能增量保存策略
详见:
- docs/SQLITE_ENHANCEMENT_IMPLEMENTATION.md
- docs/BOUNDARY_DEFENSE_IMPLEMENTATION.md
- docs/PERFORMANCE_OPTIMIZATION_COMPLETE.md
2024年11月 - 重要问题修复 🐛
- ✅ 编辑无回复提问时的边界处理
- 编辑未改动内容时不再生成冗余版本
- 无回复的提问也可触发首次生成
- ✅ 删除提问分支时路径错误修复
- 删除分支后自动重建有效路径
- 智能选择下一个可用分支并保持路径完整性
- ✅ 多模态模型重新生成时图片显示修复
- 统一处理多种图片格式(URL、base64、data URI、inline_data 等)
- 支持 OpenRouter 各种模型的图片返回格式
- 增强去重逻辑,避免重复渲染
详见:docs/RECENT_FIXES_2025_11.md
2024年11月 - 代码质量提升 📝
- ✅ 代码注释完善
- ChatView.vue:新增 800+ 行详细注释(26% 覆盖率)
- 清理 20+ 非关键调试日志,保留所有错误日志
- 组件注释质量审查:12 个活跃组件全部通过
- ✅ 组件归档管理
- 归档 3 个未使用组件(HelloWorld、ModelSelector、StartupSplash)
- 更新 tsconfig.json:排除 archived-components 目录
- TypeScript 严格模式:零编译错误
详见:
核心功能
- ✅ 分支化对话系统:支持树形对话历史和多版本回复切换
- ✅ 高级模型选择器:收藏、筛选、搜索、排序功能
- ✅ 消息编辑功能:双击编辑用户消息并重新生成 AI 回复
- ✅ 多模态支持:完整的图片上传、预览、编辑功能
- ✅ 项目管理系统:对话分类、筛选、移动
- ✅ 网络搜索集成:OpenRouter Web 搜索插件
- ✅ 自定义指令:系统提示词配置
- ✅ 采样参数配置:Temperature、Top-P、Top-K 等
- ✅ 思维链推理:支持推理模型
架构优化
- ✅ SQLite 数据库迁移:从 JSON 到 SQLite
- ✅ FTS5 全文搜索:中英文分词支持
- ✅ Web Worker 架构:数据库异步操作
- ✅ 边界防御机制:Vue Proxy 处理
- ✅ 性能优化:标签切换 75% 提升
多提供商支持
- ✅ ChatView 多提供商逻辑适配
- ✅ OpenRouter BaseURL 持久化
- ✅ Provider 切换自动刷新模型列表
- ✅ SSE 缓冲区溢出防护
- ✅ 速率限制友好提示
- ✅ API Key 格式校验
- ✅ 智能默认模型选择
UI/UX 增强
- ✅ 收藏模型滚动动画
- ✅ 智能菜单定位(Teleport + 防溢出)
- ✅ 自适应分位数滑块
- ✅ 系统默认应用打开图片
- ✅ 响应式布局优化
AI 能力增强
- 支持更多 AI 提供商(Anthropic Claude API, Azure OpenAI, 本地模型)
- 流式思维链可视化显示
- 多模态增强(音频、视频支持)
- AI 辅助代码执行和调试
- 对话模板系统
功能扩展
- 对话导出/导入功能(JSON, Markdown, PDF, Word)
- 代码块一键复制增强
- 对话标签和标记系统
- 高级搜索和过滤
- 成本统计和使用量追踪
- 语音输入/输出支持(TTS/STT)
- 插件系统架构
UI/UX 改进
- 主题系统(暗色模式、自定义主题)
- 多语言支持(i18n)- 英语、中文、日语等
- 窗口布局自定义
- 快捷键系统完善
- 拖拽排序和组织
性能和稳定性
- 更大规模对话性能优化(10000+ 消息)
- 数据库索引优化
- 离线模式支持
- 自动备份和恢复
- 错误日志系统
开发者体验
- 完整的单元测试覆盖
- E2E 测试框架
- CI/CD 流水线
- 自动化文档生成
- 开发者 API 文档
Q: 为什么选择 Starverse 而不是其他 AI 客户端?
A: Starverse 的独特优势:
- 🌳 分支化对话系统:探索不同对话路径,不丢失任何上下文
- 🔍 强大的全文搜索:快速找到历史对话内容
- 🤖 多提供商支持:一个应用访问所有主流 AI 模型
- 💾 本地数据存储:完全掌控您的数据隐私
- ⚡ 企业级性能:经过深度优化,流畅体验
Q: 我的数据存储在哪里?
A: 所有数据都存储在您的本地计算机:
- Windows:
C:\Users\<用户名>\AppData\Roaming\starverse\ - macOS:
~/Library/Application Support/starverse/ - Linux:
~/.config/starverse/
数据包括:
- SQLite 数据库文件(
chat.db) - 配置文件(electron-store)
- 完全离线可用,无需担心隐私泄露
Q: 如何清理所有聊天记录?
A: 提供多种清理方式:
方法 1: 使用一键清理脚本
# Windows
.\clear-all-data.bat
# PowerShell
.\clear-all-data.ps1
# Node.js
node scripts/clear-all-data-standalone.cjs方法 2: 手动删除
删除数据目录中的 chat.db* 文件
详见:数据清理指南
Q: 支持哪些 AI 模型?
A: 支持的模型取决于您选择的提供商:
Google Gemini:
- Gemini 2.0 Flash (最新)
- Gemini 1.5 Pro / Flash
- Gemini Pro Vision (多模态)
OpenRouter (100+ 模型):
- OpenAI: GPT-4o, GPT-4 Turbo, o1
- Anthropic: Claude 3.5 Sonnet, Claude 3 Opus
- Google: Gemini Pro, Gemini Flash
- Meta: Llama 3.1, Llama 3.2
- 更多开源模型...
Q: 遇到 "better-sqlite3" 编译错误怎么办?
A: 这是原生模块编译问题,解决方法:
Windows:
# 确保安装了 Visual Studio Build Tools
npm install --global windows-build-tools
# 重新编译
npm run rebuildmacOS/Linux:
# 确保安装了编译工具
# macOS: xcode-select --install
# Ubuntu: sudo apt-get install build-essential
npm run rebuild如果仍有问题,删除 node_modules 重新安装:
rm -rf node_modules package-lock.json
npm installQ: 如何导出我的对话记录?
A: 目前支持:
- 📋 复制消息内容:右键点击消息
- 💾 备份数据库:复制
chat.db文件
即将支持(路线图):
- 导出为 Markdown 格式
- 导出为 JSON 格式
- 导出为 PDF 文档
Q: 应用占用多少存储空间?
A: 空间占用取决于使用情况:
- 应用本身: ~200MB(包含 Electron 和依赖)
- 数据库:
- 空数据库: ~100KB
- 100 个对话: ~10MB
- 1000 个对话: ~100MB
- 缓存: 可通过清理脚本清除
SQLite 的高效存储使其比 JSON 文件节省约 40% 空间。
Q: 支持哪些操作系统?
A: 跨平台支持:
| 操作系统 | 版本要求 | 状态 |
|---|---|---|
| Windows | 10 及以上 | ✅ 完全支持 |
| macOS | 10.13 及以上 | ✅ 完全支持 |
| Linux | Ubuntu 18.04+, Fedora 32+ | ✅ 完全支持 |
Q: 如何贡献代码或报告问题?
A: 欢迎参与贡献!
报告问题:
- 前往 GitHub Issues
- 搜索是否已有类似问题
- 创建新 Issue,详细描述问题和复现步骤
贡献代码:
- Fork 本项目
- 创建特性分支:
git checkout -b feature/AmazingFeature - 提交更改:
git commit -m 'Add some AmazingFeature' - 推送到分支:
git push origin feature/AmazingFeature - 开启 Pull Request
详见:贡献指南
欢迎提交 Issue 和 Pull Request!
- Fork 本项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
- 统一编码:所有文件(含 README、日志、源码)必须使用 UTF-8(无 BOM)保存。请在编辑器设置中显式启用 UTF-8,并避免切换到 ANSI/GBK 以免 emoji 和中文说明被破坏。
本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情
- Electron - 跨平台桌面应用框架
- Vue.js - 渐进式 JavaScript 框架
- Tailwind CSS - 原子化 CSS 框架
- Google Gemini - 强大的 AI 能力支持
- OpenRouter - 统一的多模型 API 网关
- Vite - 极速的前端构建工具
- Pinia - Vue 3 状态管理
- electron-store - 数据持久化
如果您觉得这个项目有帮助,请考虑给它一个 ⭐ Star!
用 ❤️ 和 ☕ 制作
© 2025 Starverse. All rights reserved.