记录从初始部署到 ERP 集成、知识库等完整开发过程
产品名称:Homelegance AI Chatbot(Ellie)
部署地址:www.homelegance.com/chat/
服务器:AlmaLinux 8.10,Apache httpd,同一台 Web 服务器
技术栈:
| 层 | 技术 |
|---|---|
| 前端 | React 19 + Vite + TypeScript + Tailwind CSS + shadcn/ui |
| 后端 | Node.js + Express + tRPC |
| ORM | Drizzle ORM(drizzle-orm/pg-core) |
| 数据库 | PostgreSQL(独立 DB 服务器,chatbot Schema) |
| AI | Anthropic Claude API(claude-sonnet-4-6) |
| 进程管理 | PM2 |
| ERP 桥接 | Python 3.12 + FastAPI(内网 127.0.0.1:8080) |
Internet
│
▼
Apache :443 (www.homelegance.com)
├─ /chat/api → ProxyPass → Node.js :3000
├─ /chat/ → Alias → /redant/web/homelegance-chatbot/dist/public/
└─ / → AJP → Tomcat(原有 Dealer Portal)
Node.js :3000 (PM2)
├─ PostgreSQL :5432 (chatbot schema — 自身数据库)
└─ FastAPI :8080 (内网 ERP 桥接)
└─ ERP PostgreSQL(只读)
/redant/web/homelegance-chatbot//redant/web/homelegance-chatbot/erp-bridge/ecosystem.config.cjs.env.productionvite.config.ts — 移除 Manus 插件,加入 base: "/chat/"
base: "/chat/",
// 删除: vitePluginManusRuntime, jsxLocPlugin, vitePluginManusDebugCollector
client/src/App.tsx — 加入 wouter Router base
import { Router } from "wouter";
<Router base="/chat"><AppRoutes /></Router>
client/src/main.tsx — API 路径使用 BASE_URL
url: `${import.meta.env.BASE_URL}api/trpc`
drizzle.config.ts — dialect 改为 postgresql
server/db.ts — MySQL → PostgreSQL 适配:
onDuplicateKeyUpdate → onConflictDoUpdateinsertId → .returning({ id: table.id })affectedRows → return 0package.json
"db:push": "drizzle-kit push"
ProxyPreserveHost On
ProxyPass /chat/api http://127.0.0.1:3000/api
ProxyPassReverse /chat/api http://127.0.0.1:3000/api
ProxyPass /chat/ !
Alias /chat /redant/web/homelegance-chatbot/dist/public
<Directory "/redant/web/homelegance-chatbot/dist/public">
Options -Indexes +FollowSymlinks
AllowOverride None
Require all granted
</Directory>
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/chat/
RewriteCond %{REQUEST_URI} !/chat/api
RewriteCond %{REQUEST_URI} !\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2|woff|ttf|eot|map|json|webp|txt)$
RewriteRule ^ /redant/web/homelegance-chatbot/dist/public/index.html [L]
| 问题 | 根因 | 解决方案 |
|---|---|---|
| 页面空白 | vitePluginManusRuntime 注入阻塞代码 |
删除所有 Manus 插件 |
| umami 400 错误 | %VITE_ANALYTICS_ENDPOINT% 未替换 |
从 index.html 删除该脚本 |
| API 404 | tRPC URL /api/trpc 绝对路径,走到 Tomcat |
改用 import.meta.env.BASE_URL |
| SPA fallback 返回 HTML 给 JS 文件 | %{REQUEST_FILENAME} !-f 检查 DocumentRoot 而非 Alias 路径 |
改用扩展名排除法 |
startSession 500 错误 |
MySQL insertId 在 PostgreSQL 不存在 |
改用 Drizzle .returning() |
PM2 不加载 ANTHROPIC_API_KEY |
env_file 在该版本 PM2 不可靠 |
改用 Node.js fs 直接解析 .env.production |
const env = loadEnv(path.resolve(__dirname, ".env.production"));
module.exports = { apps: [{ name: "homelegance-chat", env, ... }] };
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://chatbot_user:<pw>@<pg_host>:5432/homelegance_chat
JWT_SECRET=<64字符随机>
ANTHROPIC_API_KEY=sk-ant-api03-...
ERP_API_URL=http://127.0.0.1:8080
ERP_API_KEY=<随机>
Node.js sendMessage
→ 意图检测(规则正则)
→ FastAPI Bridge(127.0.0.1:8080)
→ erp_api.* 存储函数(PostgreSQL 只读账号)
→ 结果注入 System Prompt [ERP CONTEXT]
→ Claude 生成回复
erp-bridge/main.py — FastAPI 桥接服务| 端点 | 调用的 ERP 函数 |
|---|---|
GET /health |
— |
POST /catalog |
erp_api.catalog_lists(conditions jsonb, limit int) |
POST /contacts |
erp_api.contact_lists(conditions jsonb, limit int) |
POST /orders |
erp_api.sales_orders_lists(conditions jsonb, limit int) |
GET /orders/{so_id} |
erp_api.sales_order_get(so_id text) |
POST /stock |
erp_api.stock_lists(conditions jsonb, limit int) |
返回列通过 Python set[str] 常量控制,无需改查询逻辑。
server/erpClient.ts — HTTP 客户端(8 秒超时)server/erpTools.ts — 业务查询函数,返回 LLM 友好字符串6 个业务函数:
lookupOrder(soId) — 单个订单详情lookupOrdersByCustomer(cid, n) — 客户最近 N 个订单lookupOrdersByPO(poId) — PO 号查询lookupCatalog(params) — 产品目录搜索lookupStock(params) — 库存查询lookupContact(params) — 客户资料查询1. SO-XXXXX 正则 → lookupOrder()
2. "my orders/recent orders" → lookupOrdersByCustomer()
3. PO-XXXXX 正则 → lookupOrdersByPO()
4. "in stock/inventory" + 型号正则 → lookupStock()
5. 家具关键词 → lookupCatalog()
6. "customer/dealer/company" + 名称 → lookupContact()
cd /redant/web/homelegance-chatbot/erp-bridge
pip3.12 install -r requirements.txt
cp .env.example .env && vi .env
cp erp-bridge.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now erp-bridge
curl http://127.0.0.1:8080/health
erp-bridge/.env
ERP_DATABASE_URL=postgresql://chatbot_readonly:<pw>@<erp-host>:5432/<db>
ERP_API_KEY=<与 chatbot 相同的 key>
PORT=8080
DBA 需执行
CREATE USER chatbot_readonly WITH PASSWORD '<password>';
GRANT SELECT ON TABLE catalog, customers, stock_po,
sales_orders, sales_order_items, sales_order_note,
order_status, warehouse TO chatbot_readonly;
Knowledge Base First — Ellie 必须先搜索 Q&A 知识库,命中则直接返回,不调用 LLM;未命中才进入 ERP 检测 → LLM 流程。
| 表名 | 用途 |
|---|---|
chatbot.knowledge_entries |
Q&A 知识条目(管理员维护) |
chatbot.knowledge_suggestions |
自动捕获的未解答问题 |
chatbot.knowledge_products |
从 Excel/CSV 导入的产品目录 |
用户消息
↓
① 知识库搜索(keyword 匹配)
命中 → 直接返回(source: "knowledge"),useCount++
↓ 未命中
② Workflow Flow 引擎(意图检测)
命中 → 执行 Flow 节点,返回(source: "flow")
↓ 未命中
③ ERP 意图检测(6种正则)
命中 → 调用 FastAPI Bridge,结果注入 System Prompt
↓
④ Claude LLM 生成回复
↓
⑤ 自动记录 Suggestion(供管理员审核,持续扩充知识库)
knowledge.listEntries / getEntry / createEntry / updateEntry / deleteEntry
knowledge.importEntries — CSV 批量导入
knowledge.listSuggestions / promoteSuggestion / dismissSuggestion
knowledge.listProducts / importProducts
startSession 返回欢迎消息时携带 metadata.quickReplies:
{
"content": "Welcome to Homelegance! I'm **Ellie**...",
"metadata": {
"quickReplies": ["🔥 Hot Deals", "📦 Order Status", "🛋️ Product Catalog"]
}
}
前端 ChatbotWidget.tsx 读取并渲染为可点击按钮,点击后作为用户消息发送。
client/src/pages/DataSources.tsx 重写为:
Tab 1: Data Source — Q&A 条目
Tab 2: Product — 产品目录
Tab 3: Suggestions — 自动捕获的未解答问题
server/flowEngine.ts5 个 Support Flow 意图模式:
| Flow ID | 触发关键词/正则 |
|---|---|
check-order-status |
"order status", "check.*order", "where.*my order" |
track-shipment |
"tracking", "track.*shipment", "where.*package" |
submit-return |
"return", "RMA", "refund" |
cancel-order |
"cancel.*order" |
faq-deflection |
"payment", "warranty", "shipping.*time", "minimum.*order" |
执行逻辑:
workflow_nodes)shouldEscalate 标志(Escalation 节点触发时自动转人工)KB 搜索 → Flow 引擎 → ERP 意图 → LLM → 记录 Suggestion
# 本地
git add . && git commit -m "..." && git push origin master
# 服务器
cd /redant/web/homelegance-chatbot
git pull origin master
pnpm build
pm2 restart homelegance-chat
cd /redant/web/homelegance-chatbot
export $(grep -v '^#' .env.production | xargs)
pnpm db:push
pm2 delete homelegance-chat
pm2 start ecosystem.config.cjs
pm2 save
pm2 env 0 | grep ANTHROPIC # 验证
pm2 logs homelegance-chat --lines 50
grep -i "LLM\|error\|Chat\|KB\|Flow\|ERP" /var/log/pm2/homelegance-chat-out-0.log | tail -30
systemctl status erp-bridge
journalctl -u erp-bridge -f
curl -H "X-API-Key: <key>" http://127.0.0.1:8080/health
.env,安装 systemd 服务,测试 5 个端点pnpm db:push 创建 3 个新知识库表[ ] SSO 集成(Phase 4):Dealer Portal 带 JWT token 跳转 → Ellie 自动识别 dealer 身份
?sso_token=<jwt> 传入chat.startSession 验证 token,提取 customer_id、company_name、sales_rep[ ] 产品目录导入:将 Homelegance Excel 产品表转为 CSV,通过 Knowledge → Product 导入
[ ] Leads Flows / Sales Flows:文档标注为 Pending,下一开发阶段实现
| Commit | 说明 |
|---|---|
c64aee5 |
Remove Manus dependencies from frontend build |
e2b6e8b |
Fix API paths for /chat/ base URL deployment |
bc0d946 |
Fix Apache SPA fallback for Alias + RewriteRule conflict |
96a20c0 |
Fix SPA fallback: use extension exclusion instead of %1 capture |
a896b48 |
Fix all MySQL-specific APIs for PostgreSQL compatibility |
d41f355 |
Add ERP integration: FastAPI bridge + intent-aware context injection |
4bfd994 |
Allow erp-bridge .env.example to be tracked by git |
25bc2c6 |
Fix PM2 env loading: parse .env.production via fs instead of env_file |
| (latest) | Phase A/B/C: Knowledge Base, Quick Replies, Flow Engine |