wachat 是一个使用 Wails (Go + Web) 构建的跨平台 AI 聊天桌面应用。
技术栈: Go 1.22+ (后端) + Vue 3 + TypeScript (前端) + Wails v2.10.2 (框架) + SQLite (数据库)
目标: 提供轻量级、高性能的本地 AI 聊天体验,支持流式响应和会话持久化。
wachat/
├── backend/ # Go 后端 - 严格分层架构
│ ├── api.go # API Facade - 统一对外接口
│ ├── config/ # 配置层 - YAML 配置读取
│ ├── database/ # 数据库层 - SQLite 连接和迁移
│ ├── model/ # 模型层 - 数据结构定义
│ ├── repository/ # Repository 层 - 数据访问抽象
│ └── service/ # Service 层 - 业务逻辑
│ ├── ai.go # AI 服务
│ ├── chat.go # 聊天服务
│ ├── rag_service.go # RAG 文档检索服务
│ └── binary_manager.go # 二进制管理服务
├── frontend/ # Vue 3 前端 - Composition API
│ ├── src/
│ │ ├── components/ # UI 组件(单一职责)
│ │ ├── composables/ # 可复用逻辑
│ │ ├── views/ # 页面视图
│ │ └── wailsjs/ # Wails 自动生成的绑定(不要手动修改)
├── bin/ # 嵌入的二进制文件(qdrant等)
├── config.yaml # 配置文件(从 config.example.yaml 复制)
├── main.go # Wails 应用入口
└── app.go # 应用主逻辑 - 连接前后端
各层职责:
app.go: Wails 生命周期管理,前端方法绑定,事件发送,请不要在这里写复杂的业务逻辑api.go: API 外观层,初始化各层依赖,提供统一接口service/: 业务逻辑,不直接访问数据库ai.go: AI 对话服务,集成 RAG 增强chat.go: 聊天会话管理rag_service.go: RAG 文档检索服务(基于 go-rag)binary_manager.go: 管理嵌入的二进制文件(qdrant等)
repository/: 数据访问,GORM 操作封装database/: 数据库连接、迁移model/: 数据结构(DB 模型 + 业务模型)config/: YAML 配置读取和管理(使用 Viper)
Composition API + 单一职责:
views/: 页面级组件,负责布局和组合components/: UI 组件,接收 props,发出 events,无业务逻辑composables/: 可复用的逻辑(useChat, useAutoScroll)
-
错误处理: 所有错误必须向上传递,使用
fmt.Errorf包装if err != nil { return fmt.Errorf("failed to do something: %w", err) }
-
命名约定:
- 文件名: 小写+下划线 (
chat_service.go) - 结构体: 大驼峰 (
ChatService) - 方法/函数: 大驼峰(导出)或小驼峰(私有)
- 文件名: 小写+下划线 (
-
依赖注入: 通过构造函数传递依赖
func NewChatService( convRepo *repository.ConversationRepository, msgRepo *repository.MessageRepository, aiService *AIService, ) *ChatService
-
不使用全局变量: 所有依赖通过结构体字段传递
-
组件命名: 大驼峰 (
ChatSidebar.vue) -
Composition API 风格:
import { ref, computed, onMounted } from 'vue' const data = ref<Type>() const computed = computed(() => ...)
-
类型安全: 所有函数参数和返回值必须有类型标注
-
Props 定义:
interface Props { message: Message active?: boolean } const props = defineProps<Props>()
-
事件发送:
const emit = defineEmits<{ 'select-conversation': [id: string] }>()
- 使用 TailwindCSS: 优先使用 Tailwind utility classes
- 自定义样式: 使用
<style scoped>+:deep()修改子组件 - 颜色方案: 使用 Tailwind 灰色调(gray-100, gray-700 等)
-
后端:
1. 定义数据模型 (model/types.go) 2. 创建 Repository 方法 (repository/*.go) 3. 实现 Service 逻辑 (service/*.go) 4. 在 API 层暴露接口 (api.go) 5. 在 App 层绑定前端 (app.go) -
前端:
1. 运行 wails dev 自动生成 Go 绑定 2. 创建/更新 Composable (composables/*.ts) 3. 创建/更新组件 (components/*.vue) 4. 在 View 中使用 (views/*.vue)
- 修改
model/types.go中的结构体 - GORM 会在下次启动时自动迁移
- 如需手动迁移,修改
database/database.go的AutoMigrate调用
初始化配置:
- 复制
config.example.yaml为config.yaml - 修改
config.yaml中的配置项(AI API、数据库路径等) - 配置文件会被
.gitignore忽略,不会提交到版本控制
配置文件搜索顺序:
WACHAT_CONFIG_PATH环境变量指定的目录- 当前工作目录
- 当前工作目录向上查找的项目根目录(通过 go.mod 定位)
- 可执行文件所在目录
- 可执行文件向上查找的项目根目录
- 用户配置目录
~/.config/wachat
开发模式配置:
# 如果 wails dev 找不到配置文件,设置环境变量
export WACHAT_CONFIG_PATH=$(pwd)
wails dev配置结构:
ai:
base_url: "https://api.openai.com/v1"
api_key: "your-api-key"
model: "gpt-3.5-turbo"
binaries:
enabled: true
use_embedded: false
bin_path: "./bin"
startup_order:
- qdrant
- wailsproject
rag:
enabled: false # 需要 Elasticsearch 支持
top_k: 5 # 检索返回的文档数量添加新配置项:
- 在
backend/config/config.go中添加对应的结构体字段 - 在
config.Load()函数中设置默认值 - 更新
config.yaml和config.example.yaml - 使用
config.Get()获取配置
后端 → 前端:
runtime.EventsEmit(ctx, "stream:response", map[string]interface{}{
"conversationId": id,
"chunk": content,
})前端监听:
const runtime = (window as any).runtime
runtime.EventsOn('stream:response', (data: any) => {
// 处理事件
})- 位置:
frontend/src/wailsjs/ - 不要手动修改: 每次
wails dev都会重新生成 - 如何更新: 修改 Go 代码后,Wails 会自动重新生成
- Vite 7 需要 Node.js 20.19+ 或 22.12+
- 如果版本过低会有警告但仍能运行
- 必须使用
pnpm包管理器 - 安装命令:
pnpm --dir frontend install - 不要在 frontend 目录下直接运行
cd frontend && pnpm install(shell 配置问题)
- 项目处于早期阶段,暂无自动化测试
- 主要依靠手动测试
wails dev # 启动开发服务器wails build # 构建当前平台
wails build -platform darwin/amd64 # 指定平台- 位置:
build/bin/ - macOS:
.app应用包 - Windows:
.exe可执行文件 - Linux: 二进制文件
- 遵循分层架构: 不要让 Service 直接调用 Database,必须通过 Repository
- 类型安全: Go 和 TypeScript 都要保持严格的类型检查
- 错误处理: Go 代码必须处理所有错误
- 组件职责: 保持组件单一职责,逻辑放在 Composables
- 先在
service/实现业务逻辑 - 在
api.go暴露方法 - 在
app.go绑定给前端 - 运行
wails dev自动生成前端绑定 - 前端从
wailsjs/go/main/App导入使用
- 所有数据库操作必须在
repository/层 - 使用 GORM 的方法,避免原始 SQL
- 返回错误而不是 panic
- 使用 Composition API,不要使用 Options API
- 状态管理优先使用
ref和computed - 复杂逻辑提取为 Composable
- 组件通过 props 接收数据,通过 emit 发送事件
- 优先使用 Tailwind classes
- 保持颜色柔和(gray-700 而不是 black)
- 使用
prose类处理 Markdown 渲染 - 需要覆盖样式时使用
:deep()
repository/conversation.go- 添加数据访问方法service/chat.go- 添加业务逻辑api.go- 暴露接口app.go- 绑定前端- 前端
composables/useChat.ts- 调用新方法
- 找到对应的
.vue组件 - 修改
<template>中的 Tailwind classes - 如需自定义样式,添加
<style scoped>
- 查看后端
service/ai.go的StreamResponse方法 - 查看
app.go的事件发送逻辑 - 查看前端
composables/useChat.ts的事件监听
- 修改
model/types.go的结构体定义 - 重启应用(GORM 自动迁移)
- 更新相关的 Repository 方法
- 编辑
config.yaml修改配置值 - 如需添加新配置项:编辑
backend/config/config.go - 重启应用使配置生效
本地模式(推荐,更灵活):
- 将二进制文件放入
bin/目录 - 在
config.yaml中设置:binaries: enabled: true use_embedded: false bin_path: "./bin" startup_order: - your-binary
- 无需重新编译,直接运行
嵌入模式(适合打包分发):
- 将二进制文件放入
bin/目录 - 在
config.yaml中设置:binaries: enabled: true use_embedded: true startup_order: - your-binary
- 重新编译:
wails build - 二进制会被打包到应用中(增加应用体积)
wachat 集成了 RAG 功能,使用 go-rag 项目提供文档检索增强能力。RAG 可以从 Elasticsearch 中检索相关文档,并将其作为上下文添加到 AI 对话中,从而提供更准确、更有针对性的回答。
依赖关系:
go-rag (独立 Git 项目)
↓ Go Modules (local replace)
wachat/backend/service/rag_service.go
↓ 依赖注入
wachat/backend/service/ai.go
设计原则:
go-rag保持为独立的 Git 项目- wachat 通过 Go Modules 引用 go-rag
- 使用
replace指令支持本地开发 - RAG 功能可通过配置开关启用/禁用
go.mod 配置:
require (
github.com/wangle201210/go-rag/server v0.0.0-00010101000000-000000000000
)
// 本地开发时使用 replace 指令
replace github.com/wangle201210/go-rag/server => ../go-rag/server本地开发:
- 确保
go-rag项目在../go-rag目录 replace指令允许本地修改 go-rag 并立即生效- 不需要发布 go-rag 版本即可开发
生产环境:
- 发布 go-rag 到 GitHub 后,移除
replace指令 - Go Modules 会从 GitHub 拉取指定版本
启用 RAG:
rag:
enabled: true
elasticsearch_url: "http://localhost:9200"
index_name: "knowledge_base"
top_k: 5配置说明:
enabled: 是否启用 RAG(默认 false)elasticsearch_url: Elasticsearch 服务地址index_name: 文档索引名称top_k: 检索返回的文档数量(用于生成上下文)
-
初始化 (
backend/api.go):ragService, err := service.NewRAGService(ctx, ragConfig) aiService := service.NewAIService(aiConfig, ragService)
-
文档检索 (
backend/service/rag_service.go):func (r *RAGService) RetrieveWithContext(ctx context.Context, query string) (string, error) { // 使用 go-rag 检索相关文档 results := r.rag.Retrieve(ctx, query) // 格式化为上下文字符串 return formatContext(results), nil }
-
上下文增强 (
backend/service/ai.go):// 检索相关文档 ragContext, err := a.ragService.RetrieveWithContext(a.ctx, userMessage) // 将上下文作为 system 消息添加到对话前 systemMsg := &schema.Message{ Role: schema.System, Content: ragContext, } enhancedMessages = append([]*schema.Message{systemMsg}, messages...)
-
AI 生成: 使用增强后的消息调用 AI 模型
查看 RAG 是否生效:
- 检查
config.yaml中rag.enabled是否为true - 查看应用启动日志,确认 RAG 服务初始化成功
- 在
ai.go的StreamResponse方法中添加日志:if ragContext != "" { fmt.Printf("RAG Context: %s\n", ragContext) }
常见问题:
- Elasticsearch 连接失败: 检查
elasticsearch_url配置和 ES 服务状态 - 检索无结果: 确认索引中有文档,且
index_name正确 - 上下文过长: 调整
top_k值减少检索文档数量
如果需要修改 go-rag 功能:
- 在
../go-rag目录修改代码 - 由于使用了
replace指令,修改会立即生效 - 在 wachat 中重新编译测试:
go mod tidy wails dev
- 测试通过后,提交 go-rag 的修改
- 可选:发布 go-rag 新版本,更新 wachat 的依赖版本
- 在 go-rag 中添加新方法
- 在 rag_service.go 中封装:
func (r *RAGService) NewFeature(ctx context.Context, params) (result, error) { return r.rag.NewMethod(ctx, params) }
- 在 ai.go 中使用:
if a.ragService != nil && a.ragService.IsEnabled() { result, err := a.ragService.NewFeature(ctx, params) // 处理结果 }
- 不要向后兼容: 项目还未发布第一个版本,可以大胆重构
- 保持架构一致性: 新功能必须遵循现有的分层架构
- 保持轻量: 避免引入大型依赖,保持应用体积小巧
- 跨平台兼容: 考虑 macOS/Windows/Linux 的差异