# test-ai **Repository Path**: xiaochen999/test-ai ## Basic Information - **Project Name**: test-ai - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-29 - **Last Updated**: 2026-10-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SSE Demo — 流式对话(Vite + Vue 3 / Node.js / Express) 一个最小的 SSE 流式对话示例:浏览器发一句话,服务端转发到 OpenAI 兼容接口,把 token 逐块推回页面。前端是 Vite + Vue 3 单文件组件,后端是 Express。默认接本机 Ollama 的 `minicpm5-2b-16k`,不需要联网也不需要 API key。 ## 快速开始 需要 Node ≥ 20.19(Vite 8 的要求),以及本机装好 [Ollama](https://ollama.com)。 ```powershell git clone https://gitee.com/xiaochen999/test-ai.git cd test-ai ollama pull minicpm5-2b-16k # 只需一次,1.6 GB ollama serve # 如果它没在后台运行 npm install Copy-Item .env.example .env # macOS / Linux 用 cp .env.example .env ``` ### 开发:`npm run dev`,打开 http://localhost:5173 ```powershell npm run dev ``` 并行起两个进程:Express(3000,只提供 `/api`)和 Vite(5173,带 HMR)。浏览器开 **5173**,`/api` 由 Vite 代理到 3000。改 `web/` 下的文件即时热更新;改 `src/` 会自动重启 Express。 ### 生产:`npm run build && npm start`,打开 http://localhost:3000 ```powershell npm run build # Vite 构建到 dist/ npm start # Express 同时托管 dist/ 和 /api ``` `npm start` 前必须先 `npm run build`,否则页面路径是 404(启动日志会打印提示)。 `.env.example` 默认就是 Ollama;改用云端模型时改 `BASE_URL` / `MODEL` / `API_KEY` 三行即可。 代码检查:`npm run lint`(ESLint)与 `npm run format`(Prettier)。 ## 目录导航:我要改 X,就去看 Y | 我要改… | 去看 | |---|---| | 端口、模型名、上游地址 | `src/config.js` 与 `.env` | | 系统提示词、上游请求体、下游 SSE 输出格式 | `src/routes/chat.js` | | 换上游、改 SSE 行解析 | `src/lib/upstream.js` | | 中间件、静态目录、路由挂载 | `src/app.js` | | 启动方式、监听端口 | `src/server.js` | | 页面骨架、消息列表、滚动到底 | `web/src/App.vue` | | 聊天气泡(assistant 的 Markdown / user 的纯文本) | `web/src/components/ChatMessage.vue` | | 输入框、发送 / 停止 / 清空按钮 | `web/src/components/ChatComposer.vue` | | 前端状态、SSE 解析、localStorage 历史 | `web/src/composables/useChat.js` | | Markdown 渲染与净化 | `web/src/lib/markdown.js` | | 样式 | `web/src/style.css` | | Vite 配置(端口、`/api` 代理、构建输出) | `web/vite.config.js` | ## 通信协议 请求:`POST /api/chat`,body 为 ```json { "message": "用户这一句" } ``` 响应:`text/event-stream`,逐块推送 ``` data: {"token":"你"} data: {"token":"好"} data: [DONE] ``` 服务端自行拼装上游 messages(固定一条 system + 当前 user),**不保留多轮上下文**;前端的历史仅存在 localStorage 里用于展示。 ### 中断回答 流式期间发送按钮会变成「停止」。点击后前端 abort 连接,服务端在 `res` 的 `close` 事件里 abort 上游请求,模型随即停止生成(不会继续消耗 token)。**已经生成的部分保留在气泡里并写入历史**;中断不算错误,前后端都不打错误日志。 如果一个 token 都还没收到就中断,占位空气泡会被移除,历史里只留用户那条消息。中断后不能接着让它写,只能重新提问。 ## 环境变量 | 变量 | 必填 | 说明 | |---|---|---| | `PORT` | 否 | 默认 `3000` | | `BASE_URL` | 是 | 默认 `http://localhost:11434/v1`(Ollama);云端例如 `https://api.deepseek.com` | | `API_KEY` | 是 | Ollama 不校验,填 `ollama` 即可;云端填模型平台签发的 key | | `MODEL` | 是 | 默认 `minicpm5-2b-16k`(需 `ollama pull` 过);云端例如 `deepseek-chat` | 缺任一必填项时,进程在启动阶段就报错退出,不会带着空配置跑起来。 ## 已知缺陷 - 上游不可达时进程不再退出:服务端在 stderr 打 `chat 转发失败`(附带 `ECONNREFUSED` 等底层原因),本次响应以 `200` + 空流结束,页面上仍是一个空气泡。本地 Ollama 没启动时首次请求就会触发,先把 `ollama serve` 起起来。 - 请求失败时不会在页面上提示,只在 console 打错误;未收到任何 token 的 assistant 空气泡会被移除,历史里只留下用户那条消息。 - 中断后保留的是半截答案,但服务端每次都只发当前这一句,模型并不知道自己被打断过,说「接着说」不会生效,只能重新提问。 - 中断依赖 Node 侧 `res` 的 `close` 事件;如果以后在前面加反向代理(如 Nginx 开了响应缓冲),断开信号可能延迟或传不到,上游就未必能立刻停下。 - 对话历史无限增长(只影响 localStorage 体积,不影响请求)。 - 推理模型的思考内容不展示。 - `minicpm5-2b-16k` 是思考型模型:默认会先静默思考约 2–5 秒,期间页面是一个空气泡,然后正文才开始逐字出现。想跳过思考可以在上游请求体里加 `"reasoning_effort": "none"`(实测首字从约 5 秒降到 1–2 秒,回答质量可能下降)。 - 缺少 `message` 字段时返回 `200` + 空流,而不是 `400`。 - 上游返回非 2xx 时前端同样只看到空流;服务端会在 stderr 打一行 `upstream <状态码>`,便于区分 key 错、模型名错和上游故障。 - `npm start` 不会自动构建:忘记 `npm run build` 时页面 404,只有启动日志一行提示。 ## 环境备注 - npm 10.8 + Vite 8 组合下,`npm install <新包>` 可能报 `Cannot read properties of null (reading 'edgesOut')`;加 `--legacy-peer-deps` 即可(首次安装依赖后已有 lockfile,日常 `npm install` 不受影响)。 历史的审计清单在 `docs/security-notes.md`;其中的文件路径与行号基于重排前的根目录 `server.js`,已过时。