# AiChat聊天应用 **Repository Path**: hlkaiyan/ai-chat ## Basic Information - **Project Name**: AiChat聊天应用 - **Description**: 使用WorkBoddy+HY大模型开发的简单的Ai聊天应用。 代码使用go+wails编写。 输入base_url和key就可以使用。 可用于调试接口和测试接口速度。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-07-22 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AI Chat —— 桌面 AI 聊天工具(Go + Wails) 单文件 exe 的桌面 AI 聊天客户端。支持 OpenAI / Claude / Responses 三种协议,多会话、多轮上下文、流式回复、接口耗时统计、深浅双主题、调试模式。纯绿色软件,配置在 exe 同级目录,无需安装。 --- ## 一、当前进度与功能说明 ### 已实现功能 - ✅ **多协议兼容**:`openai`(`/chat/completions`)、`claude`(`/v1/messages`)、`responses`(`/responses`) - ✅ **连接设置**:Base URL、API Key、协议、MaxTokens、模型 - ✅ **模型管理**:从 `/models` 拉取列表并缓存下拉选择,也支持手动输入模型名 - ✅ **多会话**:新建 / 删除 / 重命名 / 清空;左栏按更新时间倒序;启动自动进入最近会话 - ✅ **流式对话**:后端逐块推送(SSE 解析),前端气泡实时增量显示 - ✅ **接口耗时统计**:每条回复下方显示 `耗时 · 首字TTFT · 字/秒 · 字数` - ✅ **双主题**:深色 / 浅色切换,切换后**原生窗口标题栏颜色**也跟随(Win32 CustomTheme) - ✅ **调试模式**:开启后把每条消息的完整 `REQUEST` / `RESPONSE STATUS` / 原始 SSE 写入 exe 同目录 `debug.log`,并可在每条 AI 消息上点 `🔍 查看详情` 单独查看(按消息聚合,互不影响) - ✅ **绿色软件**:配置与聊天记录放在 exe 同级目录,不写系统目录 ### 已知问题 / 待办(交接须知) 1. **配置位置有隐患**:数据目录 = `dataDir()` = exe 同级目录,因此 `config.json` / `conversations.json` 实际落在 **`build/bin/`** 下。清理 `build/` 会连带删除用户数据(本机已踩过一次)。建议迁移到不受构建影响的固定目录(如 `aichat-gui/data/` 或 `%APPDATA%/aichat-gui/`)。 2. **调试 msgId 仅在内存**:点 `🔍` 的调试信息按 `msgId` 存在前端内存 `Map`,重启或切换会话后历史消息点开为空白状态。如需持久化,要把 `msgId` 写进 `Message` 结构。 3. **无自动化测试**:仅有手动冒烟(启动 exe 看进程存活)。CLI/TUI 旧版测试已随清理删除。 4. **`.gitignore` 忽略了 `build/bin`**:IDE 文件树默认不显示被忽略目录,导致 `build/bin` 看似为空(文件实际存在)。需在 IDE 打开"显示忽略文件",或改 gitignore 才能看到。 --- ## 二、技术栈与目录结构 **技术栈** - 后端:Go 1.23+(本机 1.26.x),Wails v2.12.0(需 CGO) - 前端:原生 HTML + CSS + JavaScript(**无框架**,仅用 Vite 3 做打包),内嵌进 exe - 渲染:Windows WebView2 **目录结构** ``` aichat-gui/ ├── main.go # 程序入口:窗口/标题栏/主题配置,go:embed 前端 ├── app.go # 核心:App 结构体 + 所有 Go↔前端绑定方法 + 流式逻辑 ├── go.mod / go.sum # Go 依赖(仅 wails v2.12.0) ├── wails.json # Wails 构建配置(install/build/dev 命令) ├── .gitignore # 忽略 build/bin、node_modules、frontend/dist └── frontend/ ├── package.json # 仅 devDependency: vite@^3 ├── index.html # 页面结构(侧边栏 / 主区 / 设置弹窗 / 调试弹窗) ├── src/ │ ├── main.js # 全部前端逻辑:绑定后端方法、事件监听、渲染、调试聚合 │ └── style.css # 全部样式,深/浅主题用 CSS 变量切换 └── dist/ # wails build 自动生成(前端打包产物,被 embed) ``` > 注意:`node_modules/` 与 `build/` 是**构建期产物**,已被 `.gitignore` 忽略,不会进仓库。交接他人时对方需先 `npm install` 再 `wails build`。 --- ## 三、环境准备(第一次拿到代码) **Windows(本机目标平台)** 1. **Go**:1.23 及以上。验证 `go version`。 2. **CGO 与 C 编译器**:必须开启 `CGO_ENABLED=1`,并安装 MinGW-w64 gcc(Wails 在 Windows 用 WebView2 加载器需要 CGO)。 - 验证:`gcc --version` 3. **Wails CLI**: ```bash go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0 # 确保 $GOPATH/bin 在 PATH wails version ``` 4. **Node + npm**:Node 18+ / npm(用于打包前端)。验证 `node -v`、`npm -v`。 5. **WebView2 Runtime**:Win11 自带;Win10 需从微软官网安装「WebView2 Runtime」。 6. **整体自检**:`wails doctor` 应全绿(Go / CGO / WebView2 / Node 均无报错)。 > 跨平台说明:当前只构建 Windows 版。要出 macOS / Linux 版,需在对应系统的机器上装好各自依赖后执行 `wails build`(macOS 需 Xcode 命令行工具,Linux 需 libgtk/webkit 等)。 --- ## 四、依赖安装 ```bash # 1) 前端依赖(必须,否则 wails build 会失败) cd aichat-gui/frontend npm install # 2) Go 依赖(一般首次 build 会自动下载;也可手动) cd ../.. cd aichat-gui go mod download ``` --- ## 五、开发模式(热重载) ```bash cd aichat-gui wails dev ``` - 前端改动自动热更新,Go 改动自动重新编译重启。 - 默认会打开浏览器 DevTools,方便调试前端。 - 调试后端日志看终端输出。 --- ## 六、构建 / 打包(产出 exe) ```bash cd aichat-gui wails build ``` - 产物:`aichat-gui/build/bin/aichat-gui.exe`(约 11MB,前端已内嵌,单文件)。 - 减小体积(去掉调试符号): ```bash wails build -ldflags="-s -w" ``` - 生成 Windows 安装包(可选,需本机已装 NSIS): ```bash wails build -nsis ``` 产物为 `build/bin/aichat-gui-amd64-installer.exe`。 > 构建提示:`wails build` 时终端出现 `Not found: time.Time` 是**无害**提示(TS 类型生成的噪声),不影响产物。 ### 6.1 更换应用图标(重要,二次开发必读) > ⚠️ **这是本仓库踩过的大坑**:直接替换 `build/windows/icon.ico` 后 `wails build`,**exe 图标不会更新**,必须清 Go 构建缓存才行。详见下文「坑 2」。 Wails v2.12.0 **没有** `wails generate icon` 子命令(该命令在旧版才有,v2.12.0 执行会报 unknown command)。v2.12.0 的图标注入机制是: - 读取 `build/appicon.png`(高分辨率源,建议 1024×1024)用于某些 UI 场景; - 读取 `build/windows/icon.ico`(**真正嵌入 exe 的资源**),编译期用 `windres` 编成 `<项目名>-res.syso` 链接进二进制。 **正确换图标流程** 1. 准备一张 1024×1024 的 PNG 源图,覆盖 `build/appicon.png`。 2. 由源图生成多尺寸 `icon.ico`(必须含 16/24/32/48/64/128/256)。本机没有 ImageMagick,用 Python Pillow 生成: ```bash # 在 managed venv 里装 Pillow(约 30s) /Scripts/pip.exe install Pillow ``` ```python # gen_ico.py from PIL import Image src = Image.open("build/appicon.png").convert("RGBA") sizes = [16, 24, 32, 48, 64, 128, 256] imgs = [src.resize((s, s), Image.LANCZOS) for s in sizes] imgs[0].save("build/windows/icon.ico", sizes=[s for s in sizes]) ``` 3. **清 Go 构建缓存**(关键!否则沿用旧图标): ```bash go clean -cache ``` > 该命令只清 `~/.cache/go-build`,**不会动 `build/bin/` 里的用户数据**(config.json / conversations.json)。 4. 重新构建: ```bash cd aichat-gui wails build -ldflags="-s -w" -trimpath ``` **坑说明(为什么只换 ico 不够)** - **坑 1(早返回)**:Wails 的 `generateIcoFile` 有判断——`build/windows/icon.ico` 已存在就**跳过重新生成**,直接复用你已有的 ico。所以想改图标,要直接覆盖 `build/windows/icon.ico`,不要指望它从 `appicon.png` 自动重生。 - **坑 2(缓存,最致命)**:Go 构建缓存会把含「旧图标」的 `.syso` 资源对象缓存;`wails build`(即使带 `-trimpath`)会**复用旧缓存** → 新 exe 仍带旧图标。必须 `go clean -cache` 强制重编。 **验证图标是否真的换上了**(别只看资源管理器,它有文件级图标缓存) 用 `pefile` 解析 exe 内嵌的 `RT_ICON` 资源,与源图逐尺寸比对: ```python # verify_icon.py import pefile, struct, io from PIL import Image, ImageChops exe = "build/bin/aichat-gui.exe" pe = pefile.PE(exe) rt = pefile.RESOURCE_TYPE["RT_ICON"].value icon_dir = None for r in pe.DIRECTORY_ENTRY_RESOURCE.entries: if r.id == rt: icon_dir = r logos = {s: Image.open("logo.png").convert("RGBA").resize((s, s)) for s in (256,128,64,48,32,24,16)} for entry in icon_dir.directory.entries: for e in entry.directory.entries: data = pe.get_data(e.data.struct.OffsetToData, e.data.struct.Size) w, h = struct.unpack(" 平均差 < 24(仅亚像素缩放差异)即判定为新图标。本仓库已用此法验证 7/7 尺寸全部匹配。 **Windows 资源管理器仍显示旧图标?** 那是 Explorer 按文件路径缓存的,与 exe 无关。解决办法:按 `F5` 刷新;或把 exe 复制到**新文件名/新目录**再开;或重启「Windows 资源管理器」进程。 --- ## 七、部署 / 交付 **最简单(绿色单文件)** - 直接把 `build/bin/aichat-gui.exe` 复制给用户即可双击运行,无需安装、无需配套文件。 - 首次运行会在 exe **同级目录**自动生成 `config.json` 和 `conversations.json`。 - 用户自己填 Base URL / API Key / 选协议和模型即可使用。 **制作安装包**:见上 `wails build -nsis`。 **注意点(交接运维必读)** - 移动 / 重命名 exe 会被程序视为「新程序」(路径即身份),配置不会跟随,会在新位置重新生成空配置。 - 不要把 `build/` 当数据备份目录清理——用户数据就在 `build/bin/` 里。 - 若要随 exe 预置一份默认配置,可在分发前先运行一次填好设置,再把整个目录打包交付。 --- ## 八、配置与数据文件 **位置**:exe 同级目录(`filepath.Dir(os.Executable())`)。 | 文件 | 说明 | |------|------| | `config.json` | 用户设置(见下表) | | `conversations.json` | 所有会话及消息 | | `debug.log` | 调试模式开启时记录,每请求一段 REQUEST/RESPONSE | **`config.json` 字段** ```json { "base_url": "http://host:port/v1", // 必须以 /v1 结尾(代码会自动补) "api_key": "sk-...", "protocol": "openai | claude | responses", "model": "claude-opus-4-8", "cached_models": ["model-a", "model-b"], "max_tokens": 4096, "theme": "dark | light", "debug": false } ``` **首次启动迁移**:若 exe 同级无配置、但旧版 `%APPDATA%/aichat/` 有配置,会自动复制过来(一次性),之后纯绿色。 --- ## 九、调试与排错 **看某条消息的原始请求/回复** 1. 设置里勾选「调试模式」。 2. 发一条消息。 3. 在该 AI 消息下方点 `🔍 查看详情` → 弹窗显示该条: - `REQUEST`:方法 + URL + 掩码后的请求头 + body - `RESPONSE STATUS`:HTTP 状态码 - `RESPONSE BODY (raw SSE)`:原始 SSE 流 **空回复排查(最常见)** - 看 raw SSE 里每个事件的 `type` 字段是否匹配当前协议,否则 `extractText` 解析为空: - `openai`:`choices[0].delta.content` - `claude`:`type=content_block_delta` 且 `delta.type=text_delta` → `delta.text` - `responses`:`type=response.output_text.delta` → `delta` - 若 `RESPONSE STATUS` 非 200,body 里通常有错误原因(如 404 = base_url 路径不对,401 = key 错)。 - `debug.log` 解析为空时会打印 `WARNING` 提示。 **其他** - 终端 `WebVie2wProcess failed`:在无显示(服务器/沙箱)环境启动正常现象,非崩溃。 - 启动闪退历史 bug(已修复):曾因 `emit()` 递归调用自身导致栈溢出;现为 `runtime.EventsEmit` + 空 ctx 守卫。 --- ## 十、关键代码索引 | 文件 | 关键内容 | |------|----------| | `app.go` | `App` 结构;绑定方法 `GetConfig/SaveConfig/SetTheme/GetDataDir/FetchModels/ListConversations/GetConversation/LastConversationID/NewConversation/DeleteConversation/RenameConversation/ClearConversation/SendMessage(convID, msgID, text)`;`streamAssistant(convID, msgID)`(协议路由 + SSE 循环);`extractText(data, protocol)`(按协议解析增量);`apiBase`(规范化为恰好一个 `/v1`);`emit(event, data)`(事件推送,带 ctx 守卫);`debugEmit(msgID, label, content)`(写 debug.log + 推 `chat:debug`,事件按 msgId 聚合到对应消息) | | `main.go` | `go:embed all:frontend/dist`;窗口尺寸;`windows.CustomTheme` 用 `rgb()` 把 (r,g,b) 转成 `0x00BBGGRR` 设置深/浅标题栏 | | `frontend/src/main.js` | 调 Go 绑定方法;`EventsOn` 监听 `chat:chunk/chat:done/chat:error/chat:debug`;消息气泡渲染;调试信息按 `msgId` 聚合到 `Map`;`showDebugModal(msgId)` 弹窗;`syncDebugButtons()` 按 `cfg.debug` 显隐 🔍 按钮 | | `frontend/index.html` | 侧边栏 + 主区 + `#settingsMask` 设置弹窗 + `#debugModal` 调试弹窗 | | `frontend/src/style.css` | 主题用 `:root` / `[data-theme=light]` 的 CSS 变量;`.checkbox-field` 横排复选框;`.bubble` 自适应宽度(短消息单行);`.debug-btn` / `.debug-modal-body` 等调试样式 | --- ## 十一、二次开发指引 **新增一种协议** 1. `app.go` 的 `streamAssistant` 的 `switch a.cfg.Protocol` 增加分支,构造对应 URL 与请求体。 2. `extractText(data, protocol)` 增加该协议的解析分支。 3. `index.html` 的 `#fProtocol` 下拉加一个 `