柏悦物流智能客服系统

This commit is contained in:
songxinnuo
2026-10-27 16:07:11 +08:00
commit 3a6eece72b
27 changed files with 4679 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# Python 缓存
__pycache__/
*.py[cod]
*.pyo
.pytest_cache/
# 数据库(含聊天记录,不要提交)
*.db
*.db-journal
*.sqlite3
# 编辑器配置
.vscode/
.idea/
*.swp
# 环境变量与密钥
.env
.env.*
!.env.example # 配置示例(不含 Key),需要提交
# 自签证书与私钥(本地演示用,不要提交)
cert.pem
key.pem
# 系统文件
Thumbs.db
desktop.ini
.DS_Store
# 日志与临时文件
*.log
*.bak
*.bak-*
+220
View File
@@ -0,0 +1,220 @@
# 柏悦智能物流客服系统
基于 Flask + DeepSeek 的中欧/英国跨境物流智能客服,支持多轮对话、运费试算、运单查询、历史会话管理。
## 功能
- **多轮上下文**:能理解"那空运呢""这个多少钱"这类追问
- **知识库优先**:优先使用本地知识库回答,减少模型编造
- **追问兜底**:当前问题没命中关键词时,用上一句提问去检索
- **运费试算**:识别重量/尺寸/渠道,按报价表阶梯单价计算并展示完整明细
- **运单号查询**:发送 8~20 位运单号,返回时间轴轨迹卡片
- **历史会话**:会话保存到 SQLite,支持打开、搜索、删除
- **交互**:停止生成、复制(纯文本)、重答、多行输入、回到底部
- **中英文切换**:右上角「中/EN」按钮或快捷键 `Ctrl + L`
- **异常处理**:接口超时、Key 失效、中断时给出明确提示与重试
- **深色模式**:右上角切换,偏好本地保存
- **响应式**:窄屏下侧栏自动变为抽屉
## 报价数据
运费试算使用 **炜易达 VIP 报价表(2026-10-15 9:00 执行版)** 中的真实数据,覆盖 8 条渠道:
| 渠道 | 21KG+ | 101KG+ | 500KG+ | 2000KG+ | 参考时效 |
|---|---|---|---|---|---|
| 欧洲海运快线 | 12.3 | 10.3 | 10.2 | 10.0 | 开船后 38-45 天 |
| 义乌中欧铁路 | 15.5 | 13.5 | — | 13.5(1000+) | 发车后 36-40 天 |
| 中欧卡航 | 19.4 | 17.4 | 17.4 | 17.3 | 发车后 16-20 天 |
| 欧洲空运 | 39 | 36 | — | 37(71+) | 10-13 天 |
| 英国海运 | 12.8 | 10.9 | — | — | 约 40 天 |
| 英国铁路 | 18.0 | 16.2 | — | — | 25-30 天 |
| 中英卡航 | 21.4 | 19.4 | — | — | 20-22 天 |
| 英国空运 | 48(22+) | 42 | — | 44(51+) | 8-10 天 |
**计费规则**(均来自报价表):
- 运费 = 阶梯单价 × 计费重量,按重量落入的档位取价
- 计费重量 = 实重与材积重取大,材积 = 长×宽×高(cm) ÷ 6000(1CBM = 167KG)
- 最低起收档 21KG(英国空运 22KG)
- 铁路、卡航单件计费重不足 13KG 加收超轻费 5 元/kg;空运单件不足 12KG 按 12KG 计费
- 偏远地址 +7 元/kg,单票最低 250 元
数据定义在 `am.py` 的 `RATES` 字典中,修改价格只需改这里,不需要动计算逻辑。
### 扩展数据(rates_extra.py)
从同一份报价表提取、目前已支持:
- **海外仓卡派按方计费**:0.5CBM+ / 1CBM+ 两档,按提问里的国家自动匹配仓点分组
- **产品附加费**:47 类产品(手机 5 元/kg、电池 1-2 元/kg、玩具 2 元/kg 等)
- **DPD 岛屿偏远判断**:12 个国家的岛屿/偏远邮编,命中即提示 35 欧/件
用法示例:
| 提问 | 效果 |
|---|---|
| 2方 海运 到德国海外仓多少钱 | 按 1CBM+ 档计算并列出德国仓点分组 |
| 寄手机到德国要加什么费用 | 给出 5 元/kg 附加费 |
| 邮编07000 算偏远吗 | 命中意大利撒丁岛,提示 35 欧/件 |
> ⚠️ 报价表含商业价格与联系人信息,公开发布前请脱敏或替换为示例数据。
## 运行环境
- Python 3.9 及以上
- Windows / macOS / Linux
## 快速启动(Windows)
双击 `start.bat` 即可,脚本会自动检查端口占用、API Key 和依赖。
## 安装与启动
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置 API Key
Key 不写进代码,通过系统环境变量提供。
Windows PowerShell:
```powershell
setx DEEPSEEK_API_KEY "sk-你的key"
```
设置后需要**重新打开终端**才生效,可用下面的命令确认:
```powershell
echo $env:DEEPSEEK_API_KEY
```
Linux / macOS:
```bash
export DEEPSEEK_API_KEY="sk-你的key"
```
Key 申请地址:https://platform.deepseek.com/api_keys
也可以参考项目根目录的 **.env.example**(不含真实 Key 的配置示例文件),其中列出了需要设置的环境变量及说明。
### 3. 启动服务
```bash
cd E:\wuliu_chat
python am.py
```
### 4. 打开页面
浏览器访问 http://127.0.0.1:5000
## 测试
### 单元测试
```bash
pytest -v
```
共 **52 个用例**,覆盖运费意图解析、运单号识别、阶梯价计算、卡派按方、附加费识别、岛屿邮编、卡片转换等。
测试在临时目录中运行,不会改动 `history.db`。
### 回答长度控制
- 中文回答:120~350 字;英文回答:120~700 字
- 超过上限时由代码自动压缩一次(保留全部数字),仍超标再压一次
- 提示词之外的第二道保险,确保字数可控
### 场景测试
`测试记录.md` 记录了 **29 个业务场景**的输入、预期结果、首次结果、修复动作与复测结果,
其中 26 项通过或已修复,3 项列为未解决/待验证(超长输入、并发提问、无答案兜底)。
## 目录结构
```
wuliu_chat/
├── am.py 主程序(路由、知识库、对话逻辑、运单查询、运费试算)
├── rates_extra.py 报价表扩展数据(卡派按方、附加费、岛屿邮编)
├── test_am.py 单元测试(52 个用例)
├── start.bat Windows 一键启动脚本
├── requirements.txt 依赖清单
├── README.md 说明文档
├── knowledge.md 知识资料(由 knowledge_list 导出)
├── 测试记录.md 场景测试记录(29 个场景)
├── .gitignore Git 忽略规则
├── .env.example 配置示例(不含 Key)
├── history.db SQLite 数据库(会话与消息)
├── static/ 静态资源
│ └── fa.jpg 网站图标
├── templates/
│ └── index.html 前端页面
└── .vscode/
└── launch.json VS Code 调试配置
```
> `kefu.py`、`SQLite.py` 为早期遗留文件,不影响运行,可安全删除。
## 主要接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/` | 聊天页面 |
| GET | `/stream_chat?msg=&conv_id=` | 流式对话(SSE) |
| POST | `/api/new_chat` | 新建会话,返回 `{"id": 会话id}` |
| GET | `/api/get_history` | 会话列表 |
| GET | `/api/get_messages?conv_id=` | 单个会话的全部消息 |
| DELETE | `/api/del_history?id=` | 删除会话(级联删消息) |
| DELETE | `/api/clear_all` | 清空全部历史 |
### 流式事件格式
`/stream_chat` 返回 Server-Sent Events,每帧是一条 JSON:
```
data: {"t": "正文片段"} 正常回答片段
data: {"e": "错误提示"} 出错时
data: {"card": {...}} 运单轨迹卡片
data: {"d": 1} 正常结束
```
## 数据库
SQLite,文件为 `history.db`,首次启动自动创建。
- `chat_history`:会话(id、title、ctime、utime)
- `messages`:消息(id、conv_id、role、content、ctime)
运单卡片以 `__CARD__{json}` 形式保存在 `messages.content`,前端识别后重新渲染。
## 常见问题
**页面报"服务未配置 API Key"**
环境变量没读到。确认变量名是 `DEEPSEEK_API_KEY`,并**重新打开终端**后再启动。
**页面报"API Key 无效或已过期"**
Key 已作废,去平台重新生成一把,再用 `setx` 覆盖。
**端口 5000 被占用**
```powershell
netstat -ano | findstr ":5000 "
taskkill /PID <进程号> /F
```
确认只有一行 `LISTENING`,避免两个进程同时抢端口。
**改了 am.py 没生效**
Flask 启动时加载代码,改后端必须重启服务;只改 `index.html` 则刷新页面即可。
## 说明
- 运单轨迹当前为**模拟数据**(按单号固定生成),接真实接口只需修改 `am.py` 中的 `build_track()`。
- 服务使用 Waitress 多线程启动(8 个线程),适合内网小范围使用;面向公网仍需反向代理与 HTTPS。
- 默认只监听 `127.0.0.1`,如需局域网访问,把 `am.py` 中的 `HOST` 改为 `"0.0.0.0"`。
- 运费计费方式为**阶梯单价 × 计费重量**,价格与时效来自炜易达报价表。报价更新时只需修改 `RATES`;知识库文案在 `knowledge_list` 中,两者建议同步更新。
- 材积按 ÷6000 计算(1CBM = 167KG),与部分渠道 ÷5000 的口径不同,修改时注意核对报价表。
+1475
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{"conv_id":295}
+146
View File
@@ -0,0 +1,146 @@
# 柏悦智能物流客服系统 · 知识资料
> 本文件由 `am.py` 的 `knowledge_list` 自动导出,供评审与知识维护使用。
> 数据来源:炜易达 VIP 10月15日9点执行报价表。
> 修改知识内容请直接编辑 `am.py` 中的 `knowledge_list`,改完后重新导出本文件。
---
## 目录
1. 公司介绍(1 条)
2. 联系方式与营业时间(2 条)
3. 渠道与价格(7 条)
4. 计费规则(4 条)
5. 收货与交仓(2 条)
6. 产品与禁限运(1 条)
7. 清关与报关(2 条)
8. 理赔与退件(2 条)
9. FBA 与海外仓(1 条)
合计 **22** 条知识条目。
---
## 1. 公司介绍
### 公司、柏悦、柏悦物流、介绍、哪家、哪家货代、你们是哪家
浙江柏悦物流位于义乌,主营中欧跨境物流专线,依托义乌、宁波口岸优势,服务中小跨境卖家。主要渠道:欧洲海运快线、义乌-中欧铁路、中欧卡航、欧洲空运(空派)、英国空运,均支持欧洲、英国双清包税与VAT递延。义乌、泉州、广州、深圳均可交仓。
---
## 2. 联系方式与营业时间
### 人工客服、人工、客服、转人工、在线吗、上班、工作时间、营业时间、几点、周六、周日、星期六、星期日、星期天、礼拜、周末、双休、节假日、放假、休息
人工客服在线时间:**周一至周五 9:00—18:00**。周六、周日及法定节假日人工客服不上班。非工作时间可以先留言或提交需求,业务员会在下一个工作日回复您。
### 电话、联系方式、怎么联系、联系你们、微信
可以联系您的专属业务员,或在人工客服在线时间(周一至周五 9:00—18:00)通过在线客服咨询。建议提前准备好运单号、货物信息(品名、重量、尺寸、目的国),方便快速处理。
---
## 3. 渠道与价格
### 海运、船运、海派、海运快线、樱花快线
欧洲海运快线(双清/递延)单价:21KG+ 12.3元/kg,101KG+ 10.3元/kg,500KG+ 10.2元/kg,2000KG+ 10.0元/kg。时效:开船后38-45天提取。可走樱花快线(+0.3元/kg),递延价减1.7元/kg。主派送为UPS/DHL/DPD/CWE统配。
### 铁路、中欧班列、班列、义乌铁路、火车
义乌-中欧铁路专线单价:21KG+ 15.5元/kg,100KG+ 和 1000KG+ 均为13.5元/kg。时效:发车后36-40天提取。VAT递延减1元/kg。只限送义乌仓,义乌装柜经阿拉山口/霍尔果斯出境。
### 卡航、卡车、卡派
中欧卡航单价:21KG+ 19.4元/kg,101KG+ 和 500KG+ 17.4元/kg,2000KG+ 及 4000KG+ 17.3元/kg。时效:发车后欧盟16-20天提取。递延减1.5元/kg,普卡时效18-22天。纯纺织品另加5元/kg。
### 空运、空派、航空、欧洲空运
欧洲空运专线(空派)单价:21KG+ 39元/kg,45KG+ 38元/kg,71KG+ 37元/kg,101KG+ 36元/kg。时效:10-13天左右提取;快线8-13天,直飞快线12-15天。一票多件单件不足12KG按12KG计费。
### 英国海运、英国铁路、中英卡航、英国专线
英国专线(DPD)双清价格:英国海运21KG+ 12.8元/kg、101KG+/301KG+ 10.9元/kg,时效约40天提取;英国铁路21KG+ 18.0元/kg、101KG+ 16.2元/kg,发车后25-30天提取;中英卡航21KG+ 21.4元/kg、101KG+ 19.4元/kg,发车后20-22天提取。VAT自税/递延价更低(海运10.0/8.1、铁路15/13.2、卡航18.4/16.4)。北极航线英国海运25-28天,价格单询。卡航单件不超30kg、尺寸≤98×68×59cm。
### 英国空运、英国、UK、伦敦
英国空运专线(双清)单价:22KG+ 48元/kg,51KG+ 44元/kg,101KG+ 42元/kg,时效8-10个自然日提取。VAT自税/递延为44/40/38元/kg,带电货47.5/42.5/41.5元/kg。拒收北爱尔兰、苏格兰高地等偏远地址。
### 时效、多久、要几天、多长时间
各渠道时效:欧洲海运快线开船后38-45天提取;中欧铁路发车后36-40天提取;中欧卡航发车后16-20天提取;欧洲空运10-13天提取;英国空运8-10个自然日提取。海关查验、罢工、节假日等不可抗力导致的时间不计入。
---
## 4. 计费规则
### 报价、价格、费用、计费、怎么收费、计费重量、多少钱、运费、单价
运费计算方式:阶梯单价 × 计费重量。计费重量 = 实重与材积重取大值,材积 = 长×宽×高(cm)÷6000,即1CBM=167KG。各渠道最低起收21KG(英国空运22KG)。单件计费重不足13KG的渠道(铁路、卡航)按5元/kg加收超轻费;空运单件不足12KG按12KG计费。偏远地址加7元/kg,单票最低250元。价格不含税,税点以开出的发票为准。
### 超轻费、计费重量不足、最低计费
单件计费重量不得低于13KG(空运12KG),可整票按平均13KG算,不足部分按5元/kg加收超轻费。例:单箱只有12KG,加收5×1=5元超轻费。欧洲热门仓卡派直送要求单件平均计费重12KG,不足按2元/kg加收派送费。
### 偏远、偏远费、偏远地址
偏远地址加收7元/kg(不含燃油),单票偏远费最低消费250元/票。需要自行到UPS/DPD官网查询目的地址是否偏远,我司出货时不另行通知,以账单为准,偏远费6个月内有效。
### 超重、超长、超大、尺寸限制
欧洲空运最长边≤99cm、次长边≤75cm、实重<24KG。英国DPD单件≤30KG,尺寸100×70×60cm以内,超重或超长各加收330元/件。UPS单边最长≤99cm、次边≤75cm、单件<24KG,超出加收240元+100元。热门仓卡派直送:单件超40KG加收30元/件。
## 5. 收货与交仓
### 揽收、上门取件、上门收货、交仓、交货
义乌、深圳(龙华观澜、宝安福园)、广州(石井)、泉州均可交仓,义乌仓10:00-21:00收货。欧洲空运交仓义乌+1元/kg,泉州、广州+0.5元/kg;海运、铁路、卡航泉州交货+0.5元/kg。
### 入仓、进仓、仓库收货、仓库地址、仓库在哪、义乌仓、交仓地址、送仓
义乌仓:义乌市稠江街道梦娜袜业德贸路东三门进,李东文 19558263692(10:00-21:00)。深圳龙华:龙华区观澜街道君子布社区张一村路黄背坑路1号B栋,张长印 18688937838。深圳宝安:福园二路九号海亨通物流B区C09-C14。广州:恒河沙仓储石井仓211,潘志红 13789206500。送货需打印交接清单随货交仓。
## 6. 产品与禁限运
### 敏感、带电、电池、液体、粉末、禁品、不能寄、违禁品、拒收、拒接
可接带标准箱100瓦以下非危电池/内电产品(C卡航、海运快线均可),资料齐全加1元/kg,资料不齐加2元/kg;手机、平板电脑加5元/kg。拒接:纯液体、纯粉末、药品、食品、易燃易爆、仿牌侵权、木制品(空运)、陶瓷手表等反倾销品、电子烟、管制刀具、枪支。电池第一个托收400元/件,第二个起100元/件,600元封顶。
## 7. 清关与报关
### 清关、递延、VAT、双清、包税、DDP
欧洲、英国渠道均支持双清包税与VAT递延(只付关税,增值税销售后缴纳)。递延需提供法人护照/身份证、欧盟VAT和EORI号及回执、营业执照、4份模板文件原件扫描件PDF。罗马尼亚等欧盟国家有二次清关风险,二次关税由客户自行缴纳。
### 报关、单独报关、报关费
单独报关费用:空运350元/票,海运200元/票,铁路250元/票,卡航200元/票,续页费50元/页。一达通报关500元/票。单票品名最多5个,第6个起加收30元/个。报价不含税,税点以开出的发票为准。
## 8. 理赔与退件
### 理赔、货损、丢件、赔付、破损、压坏、索赔
遗失赔付:未提取上网遗失按申报价值赔偿,最高不超过40元/kg,不退运费;已提取遗失按服务商标准,最高100USD/票。开查时间:交仓后7天未提取可申请开查,开查30天无果走索赔。签收后亚马逊未上架需2周内提出调查,包裹异常需1个月内提出。外包装无损的内件丢失不支持理赔,易碎品损坏不支持理赔。可自购保险,保费为货值千分之三(电池千分之五)。
### 退件、退回、退货、退运、改地址
海运/铁路为散货拼柜,已装柜不接受退件和扣件。空运国外扣件费200欧/票,收件人改派地址最低200元/票。退回海外仓:德国境内15欧/件,欧盟境内25欧/件,操作费100元/票,5个工作日免仓储,超期1欧/kg/天。重派德国10欧/件、欧盟20欧/件。
## 9. FBA 与海外仓
### 亚马逊、FBA、海外仓、仓库配送、卡派
支持FBA头程送亚马逊仓,热门仓卡派直送(德国WRO5/HAJ1/DTM1/DTM2、法国CDG7/LIL8、意大利TRN3、西班牙BCN8、罗马尼亚EMAG等)。卡派直送优势:超重附加费少、不限尺寸重量、上架快、基本无丢件。法国、意大利、西班牙其他亚马逊仓不能超UPS标准。
+78
View File
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""报价表扩展数据(自动生成,来源:炜易达 VIP 10月15日9点执行报价表)
包含:海外仓卡派、二级补仓、德国仓亚马逊退货、产品附加费、DPD 岛屿偏远邮编。
"""
# 海外仓卡派:按 kg(21KG+/51KG+)或按方(0.5CBM+/1CBM+)计费
# 价格单位:按 kg 为 RMB/kg,按方为 RMB/CBM
WAREHOUSE_CARD = {'谷仓法国巴黎1仓-95500-FR\n谷仓法国巴黎2仓-60440-FR\n谷仓法国巴黎4仓-77230-FR': {'卡航': {'kg21': 19.5, 'kg51': 18.5, 'cbm05': 3200.0, 'cbm1': 3020.0}, '普卡': {'kg21': 19.0, 'kg51': 18.0, 'cbm05': 3110.0, 'cbm1': 2920.0}, '铁路': {'kg21': 16.8, 'kg51': 15.8, 'cbm05': 2722.0, 'cbm1': 2542.0}, '海运': {'kg21': 12.5, 'kg51': 11.5, 'cbm05': 1952.0, 'cbm1': 1772.0}}, '4PX-69110-PL\n谷仓-69110-PL\nSLU2-69110-PL': {'卡航': {'kg21': 18.2, 'kg51': 17.2, 'cbm05': 2966.0, 'cbm1': 2786.0}, '普卡': {'kg21': 17.7, 'kg51': 16.7, 'cbm05': 2876.0, 'cbm1': 2686.0}, '铁路': {'kg21': 15.5, 'kg51': 14.5, 'cbm05': 2488.0, 'cbm1': 2308.0}, '海运': {'kg21': 11.1, 'kg51': 10.1, 'cbm05': 1700.0, 'cbm1': 1520.0}}, 'EDAPL-69100-PL': {'卡航': {'kg21': 18.6, 'kg51': 17.6, 'cbm05': 3038.0, 'cbm1': 2858.0}, '普卡': {'kg21': 18.1, 'kg51': 17.1, 'cbm05': 2948.0, 'cbm1': 2758.0}, '铁路': {'kg21': 15.9, 'kg51': 14.9, 'cbm05': 2560.0, 'cbm1': 2380.0}, '海运': {'kg21': 11.5, 'kg51': 10.5, 'cbm05': 1772.0, 'cbm1': 1592.0}}, '4PX-66340-PL\n菜鸟仓-66340-PL': {'卡航': {'kg21': 18.9, 'kg51': 17.9, 'cbm05': 3092.0, 'cbm1': 2912.0}, '普卡': {'kg21': 18.4, 'kg51': 17.4, 'cbm05': 3002.0, 'cbm1': 2812.0}, '铁路': {'kg21': 16.2, 'kg51': 15.2, 'cbm05': 2614.0, 'cbm1': 2434.0}, '海运': {'kg21': 11.9, 'kg51': 10.9, 'cbm05': 1844.0, 'cbm1': 1664.0}}, '希音-44141-PL\n希音-55040-PL': {'卡航': {'kg21': 19.5, 'kg51': 18.5, 'cbm05': 3200.0, 'cbm1': 3020.0}, '普卡': {'kg21': 19.0, 'kg51': 18.0, 'cbm05': 3110.0, 'cbm1': 2920.0}, '铁路': {'kg21': 16.8, 'kg51': 15.8, 'cbm05': 2722.0, 'cbm1': 2542.0}, '海运': {'kg21': 12.5, 'kg51': 11.5, 'cbm05': 1952.0, 'cbm1': 1772.0}}, '谷仓捷克1仓-25261-CZ\n谷仓捷克2仓-25261-CZ\n谷仓捷克3仓-41002-CZ\n谷仓捷克4仓-25261-CZ\n谷仓捷克5仓-25219-CZ\n谷仓捷克7仓-25301-CZ\n谷仓捷克8仓-41002-CZ': {'卡航': {'kg21': 18.6, 'kg51': 17.6, 'cbm05': 3038.0, 'cbm1': 2858.0}, '普卡': {'kg21': 18.1, 'kg51': 17.1, 'cbm05': 2948.0, 'cbm1': 2758.0}, '铁路': {'kg21': 15.9, 'kg51': 14.9, 'cbm05': 2560.0, 'cbm1': 2380.0}, '海运': {'kg21': 11.5, 'kg51': 10.5, 'cbm05': 1772.0, 'cbm1': 1592.0}}, '4PX布拉格A仓-25261-CZ': {'卡航': {'kg21': 19.0, 'kg51': 18.0, 'cbm05': 3110.0, 'cbm1': 2930.0}, '普卡': {'kg21': 18.5, 'kg51': 17.5, 'cbm05': 3020.0, 'cbm1': 2830.0}, '铁路': {'kg21': 16.3, 'kg51': 15.3, 'cbm05': 2632.0, 'cbm1': 2452.0}, '海运': {'kg21': 12.0, 'kg51': 11.0, 'cbm05': 1862.0, 'cbm1': 1682.0}}, '4PX马德里A仓-19171-ES': {'卡航': {'kg21': 20.8, 'kg51': 19.8, 'cbm05': 3434.0, 'cbm1': 3254.0}, '普卡': {'kg21': 20.3, 'kg51': 19.3, 'cbm05': 3344.0, 'cbm1': 3154.0}, '铁路': {'kg21': 18.1, 'kg51': 17.1, 'cbm05': 2956.0, 'cbm1': 2776.0}, '海运': {'kg21': 13.7, 'kg51': 12.7, 'cbm05': 2168.0, 'cbm1': 1988.0}}, '盘古-63150-DE\n新欧达-31628-DE': {'卡航': {'kg21': 19.4, 'kg51': 18.4, 'cbm05': 3182.0, 'cbm1': 3002.0}, '普卡': {'kg21': 18.9, 'kg51': 17.9, 'cbm05': 3092.0, 'cbm1': 2902.0}, '铁路': {'kg21': 16.7, 'kg51': 15.7, 'cbm05': 2704.0, 'cbm1': 2524.0}, '海运': {'kg21': 12.3, 'kg51': 11.3, 'cbm05': 1916.0, 'cbm1': 1736.0}}}
# 二级补仓(海外仓中转)主要服务费
SECONDARY_WAREHOUSE = {'服务': '1.贴标:2张FBA标签,一张快递面单)单件计费重量范围在(0KG<X≤30KG) 2.打板:实际打板数为准(1个板大概1.2方-1.5方) 3.卸货费', '头程单价指示': '欧洲铁路头程单价:欧洲铁路渠道 中欧卡航头程单价:中欧卡航渠道 欧洲海运头程单价:海运快线/海运热门仓卡派直送 英国头程单价:英国专线(DPD)', '二次派送': '1. 欧洲/英国二级补仓无需二次派送费用,可自提单价-1/KG 2. 海外仓在德国,如需发往其他国家补运费差价', '仓储费': '德国:免费存储25天,超过5RMB/方/天,61-180天 7RMB/方/每天,181天以上 15RMB/方/每天 英国:免费存储25天,超过8RMB/方/天,90-120/方/天 9.5RMB/方/天,超120天 15RMB/方/天', '订单处理费': '1:针对一个SKU发往一个地址作为一个订单收取15RMB订单操作费; 2:针对不同票数合并发往一个地址作为一个订单收取15RMB订单操作费; 出货之前都需要填写出货指令', '销毁费': '德国:单询 英国:50英镑/件', '换产品SKU费': '欧洲:针对一个箱里面是一种SKU要换标:开箱关箱费13RMB/箱换SKU:4RMB/个,清点:2RMB/个针对一箱多个SKU换标(费用单询) 英国:针对一个箱里面是一种SKU要换标:开箱关箱费20RMB/箱换SKU:6RMB/个,清点:3RMB/个针对一箱多个SKU换标(费用单询)'}
# 德国仓亚马逊退货费用(单位:欧元)
DE_RETURN_FEES = [
{'cat': '2. Outbound 出库', 'item': '2.1.1 Picking 整箱出库', 'sub': 'Single Carton 每箱', 'price': 2},
{'cat': '4. In-House Service 库内服务', 'item': '4.4 Packing 打包费', 'sub': 'Per Carton 每箱', 'price': 1.5},
{'cat': '4. In-House Service 库内服务', 'item': '4.5 Unpacking and Inspection 开箱检验', 'sub': 'Per Carton 每箱', 'price': 1},
{'cat': '4. In-House Service 库内服务', 'item': '4.6 Destroy Product without Battery 不带电池产品销毁', 'sub': 'Per 1KG 每公斤', 'price': 1},
{'cat': '4. In-House Service 库内服务', 'item': '4.7 Destroy battery 销毁电池', 'sub': 'Per 1KG 每公斤', 'price': 1.5},
{'cat': '4. In-House Service 库内服务', 'item': '4.8 Cancel Order 截单费', 'sub': 'Per order 每单', 'price': 2},
{'cat': '4. In-House Service 库内服务', 'item': '4.9 Claim Fee 认领费', 'sub': 'Every order 每个订单', 'price': 2},
{'cat': '4. In-House Service 库内服务', 'item': '4.10 Lost Shipping Claim 丢件索赔服务费', 'sub': 'Per order 每单', 'price': 5},
]
# 产品附加费清单
PRODUCT_SURCHARGES = [
('电池 标准箱100瓦以下非危电池/内电不超100瓦产品。头程参考海运快线快递单价标准', '资料齐全+1/KG 资料不齐全+2/KG'),
('手机,平板电脑', '5/KG'),
('铝制品', '2元/KG'),
('瓷砖', '1元/KG'),
('玻璃纤维网格织物、玻璃纤维长丝、聚酯高强力纱', '1元/KG'),
('胶合板', '1元/KG'),
('铜版纸', '1元/KG'),
('钢丝绳和钢缆', '1元/KG'),
('包包、U型枕、吊床,普通纺织品', '1元/KG(中欧卡航+2)'),
('与接触食品类产品(厨具、榨汁机、擀面杖、杯子等)', '1元/KG'),
('LED灯、白炽灯、钨丝灯类、电子温度计', '1元/KG'),
('皮革制品', '1元/KG'),
('球类', '1元/KG'),
('木制品', '1元/KG'),
('带磁产品', '1元/KG'),
('蓝牙产品 无线产品 数据线', '1元/KG'),
('金属及其制品:不锈钢无缝钢管耐腐蚀钢、铸铁产品、可锻铸铁螺纹管和接头、钢铁管配件、钢铁制紧固件、预应力非合金钢丝和钢绞线、活页夹的贱金属附件、铸铁井盖', '1元/KG'),
('光伏、晶体硅光伏组件、太阳能板、眼镜', '2元/KG'),
('玩具、沐浴盐球、碳包 蜡烛', '2元/KG'),
('厨房刀具 (全长不超30CM)', '2元/KG'),
('空气净化器', '2元/KG'),
('工具箱', '2元/KG'),
('眼镜类 笔类', '2元/KG'),
('机顶盒 计时器(不包括大的钟表) 电子手环', '2元/KG'),
('带陶瓷产品', '2元/KG'),
('汽车/摩托车/自行车配件/平衡车/汽车配件', '2元/KG'),
('卡客车轮胎、铝合金轮毂 、笔类', '2元/KG'),
('投影仪', '2元/KG'),
('成人用品、性用品、', '3元/KG'),
('服装类(大衣,棉服,风衣,外套,斗篷,运动服饰,毛衫,T恤,衬衫,连体服饰,套装,裙子,骑行服),毛巾类,床上用品,和内衣内裤(海关编码61-65开头)', '3/KG(中欧卡航+5)'),
('鞋类(包括塑料拖鞋、鞋垫)不包括鞋带', '3/KG(中欧卡航+5)'),
('针织类的帽子、围巾、手套、披肩,窗帘', '3/KG(中欧卡航+5)'),
('U盘/内存卡', '3元/个'),
('各种码表 手表', '3.5元/个'),
('车载DVD/车载显示屏/车载收音机/行车记录仪/行车导航仪', '80元/台'),
('投影仪', '35元/台'),
('平衡车/平板车(单个15KG以下)(超100瓦安时拒接)', '单询'),
('平衡车/平板车(超100瓦安时拒接)', '单询'),
('电动滑板车(单个15KG以下)(超100瓦安时拒接)', '单询'),
('电动滑板车(超100瓦安时拒接)', '单询'),
('平衡车/平板车/滑板车(单个20KG以上)', '单询'),
('自行车、电动自行车、', '400/台(计费重超60KG拒收)'),
('药物、化学产品及原料', '拒接'),
('玩具车、积木类', '不侵权+2/KG'),
('管制刀具,弓箭,枪支弹药及配件', '拒接'),
('电子打火机、电子烟', '拒接'),
('以上表格未列明的产品', '另外询价'),
]
# DPD 欧洲岛屿偏远邮编(附加费 35 欧/件)
DPD_ISLAND_POSTCODES = {'丹麦': {'code': 'DK', 'text': '4592, 5960, 5970, 5985, 6720, 7884,8305,8592, 9940, 9950, 9960, Bornholm: 3700, 3720, 3730,3740,3751, 3760, 3770, 3782, 3790'}, '德国': {'code': 'DE', 'text': 'Hiddensee: 18565, NordfriesischeInseln:25849, 25859, 25863, 25869, 25938, 25946, 25980,25992,25996–25999, Ostfries. Inseln: 26465, 26474, 26486,26548,26571, 26579, 26757, Helgoland: 27498,27499,Chiemsee: 83256 Neuwerk:'}, '爱沙尼亚': {'code': 'EE', 'text': '62601, 88001–88005, 91217,91301–91320,92001–92420, 93001–94799, 96027, 96098'}, '芬兰': {'code': 'FI', 'text': 'Aland–Inseln: 22000–22999'}, '法国': {'code': 'FR', 'text': '17111, 17123, 17190, 17310, 17370,17410, 17480,17550, 17580, 17590, 17630, 17670, 17740, 17880,17940,22870, 29242, 29253, 29259, 29990, 56360, 56590,56780,56840, 85330, 85350, Korsika: 20000–20999'}, '希腊': {'code': 'GR', 'text': 'Saronische Inseln: 180xx, 189xx,Ionische Inseln: 28xxx–29xxx, 31xxx, 49xxx, Berg Athos:63086–63087,Kreta: 70xxx–74xxx und Inseln der Ägäis: 80xxx–85xxx'}, '意大利': {'code': 'IT', 'text': 'Pontinische Inseln: 04020, 04027, Sardinien:07000–09999, Lagune von Venedig: 30010, 30012, 30100,30121–30126, 30131–30133, 30135, 30141, Elba mitTyrrhenischeInseln: 57030–57039, 58012–58013, 58018, Tremiti Inseln:71040, Inseln im Golf von Neapel: 80070–80071, 80073–80077, 80079, Inselgruppen um Sizilien: 90010, 91017, 91023,92010, 98050, 98052, 98055Einige PLZ können Gebiete auf Inseln und dem Festlandbetreffen.'}, '克罗地亚': {'code': 'HR', 'text': '20221–20226, 20260–20263, 20264,20270– 20275, 20290, 21220, 21223–21225, 21400,21403–21405,21410, 21412, 21413, 21420, 21423–21426, 21430, 21432,21450, 21454, 21460, 21462–21463, 21465–21467, 21469,21480, 21483, 21485, 22231–22236, 22240–22244, 23212,23234, 23249–23251, 23262–23264, 23271–23275, 23281–23287, 23291–23296, 51280–51281, 51500, 51511–51517,51521–51523, 51550–51552, 51554–51557, 51559, 51561–51562, 51564, 53291, 53294, 53296'}, '荷兰': {'code': 'NL', 'text': '1156AA–1156ZZ, 1791AA–1797ZZ,8881AA– 8884ZZ, 8891AA–8897ZZ, 8899AA–8899ZZ,9161AA–9164ZZ, 9166AA–9166ZZ'}, '葡萄牙': {'code': 'PT', 'text': 'Madeira: 9000–9390, Porto Santo: 9400,Azoren: 9500–9980'}, '瑞典': {'code': 'SE', 'text': '10005, 13025, 13033, 13034, 13036,13038, 13039, 13042, 13043, 13055, 13056, 37022, 43080–43099,45204–45205, 47112–47115, 47500–47599, 62000–62999,74296–74297, 76017–76019, 91803'}, '西班牙': {'code': 'ES', 'text': 'Balearen: 07000–07999'}}
BIN
View File
Binary file not shown.
+74
View File
@@ -0,0 +1,74 @@
@echo off
chcp 936 >nul
title Baiyue Logistics AI Customer Service
cd /d "%~dp0"
echo ============================================
echo Baiyue Logistics AI Customer Service
echo ============================================
echo.
where python >nul 2>nul
if errorlevel 1 (
echo [ERROR] python not found. Install Python and tick "Add to PATH".
echo.
pause
exit /b 1
)
echo [1/3] Checking port 5000 ...
netstat -ano | findstr "LISTENING" | findstr /C:":5000 " >nul
if not errorlevel 1 (
echo.
echo [INFO] Port 5000 is already in use, a server is probably running:
echo.
netstat -ano | findstr "LISTENING" | findstr /C:":5000 "
echo.
echo The last column is the process id. To stop it:
echo taskkill /PID 12345 /F
echo.
start "" http://127.0.0.1:5000
echo Browser opened. Press any key to close this window.
pause
exit /b 0
)
echo OK - port is free.
echo [2/3] Checking DEEPSEEK_API_KEY ...
if "%DEEPSEEK_API_KEY%"=="" (
echo.
echo [WARN] DEEPSEEK_API_KEY is empty in this window.
echo If you just ran setx, reopen this window or restart VS Code.
echo setx DEEPSEEK_API_KEY "sk-xxxxxxxx"
echo.
pause
) else (
echo OK - key detected.
)
echo [3/3] Checking dependencies ...
python -c "import flask, requests" >nul 2>nul
if errorlevel 1 (
echo Missing packages, installing ...
python -m pip install -r requirements.txt
if errorlevel 1 (
echo [ERROR] pip install failed, check network and retry.
pause
exit /b 1
)
)
echo OK - dependencies ready.
echo.
echo ============================================
echo Starting: http://127.0.0.1:5000
echo Close this window or press Ctrl+C to stop
echo ============================================
echo.
start "" http://127.0.0.1:5000
python am.py
echo.
echo Server stopped.
pause
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

+174
View File
@@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>柏悦物流 - 管理后台</title>
<style>
body{font-family:-apple-system,"Microsoft YaHei",sans-serif;background:#f0f4fa;margin:0;padding:20px;}
h1{color:#18479c;}
.card{background:#fff;border-radius:12px;padding:20px;margin-bottom:16px;box-shadow:0 2px 8px rgba(0,0,0,0.06);}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;}
.stat{background:linear-gradient(135deg,#e8f0ff,#fff);border-radius:10px;padding:14px;text-align:center;}
.stat .num{font-size:24px;font-weight:bold;color:#2978e8;}
.stat .label{font-size:12px;color:#666;}
.layout{display:grid;grid-template-columns:220px 1fr;gap:16px;}
#sessionList{max-height:400px;overflow-y:auto;}
.session-item{padding:10px;border-radius:8px;cursor:pointer;margin-bottom:6px;background:#f5f8fc;font-size:13px;}
.session-item.active{background:#2978e8;color:white;}
.session-item .badge{background:#dc2626;color:white;border-radius:10px;padding:1px 6px;font-size:11px;margin-left:4px;}
#chatBox{height:350px;overflow-y:auto;background:#f8fafc;border-radius:8px;padding:10px;margin-bottom:10px;}
.msg{margin-bottom:8px;padding:6px 10px;border-radius:8px;max-width:70%;font-size:14px;}
.cust{background:#dbeafe;margin-right:auto;}
.agent{background:#dcfce7;margin-left:auto;}
.btn{background:#2978e8;color:#fff;border:none;padding:6px 14px;border-radius:6px;cursor:pointer;font-size:13px;}
.btn:hover{background:#18479c;}
.btn.red{background:#dc2626;}
input[type=text]{width:75%;padding:8px;border:1px solid #ddd;border-radius:6px;}
table{width:100%;border-collapse:collapse;font-size:13px;}
th,td{padding:6px 10px;border-bottom:1px solid #eee;text-align:left;}
</style>
</head>
<body>
<h1>📊 柏悦物流客服后台</h1>
<div class="grid">
<div class="stat"><div class="num" id="s1">-</div><div class="label">总对话数</div></div>
<div class="stat"><div class="num" id="s2">-</div><div class="label">总消息数</div></div>
<div class="stat"><div class="num" id="s3">-</div><div class="label">今日新增</div></div>
<div class="stat"><div class="num" id="s4">-</div><div class="label">👍</div></div>
<div class="stat"><div class="num" id="s5">-</div><div class="label">👎</div></div>
</div>
<div class="layout">
<div class="card">
<h3>进行中对话</h3>
<div id="sessionList"></div>
<h3 style="margin-top:16px;">待接入</h3>
<div id="queue"></div>
</div>
<div class="card" id="chatCard" style="display:none;">
<h3 id="chatTitle">对话</h3>
<div id="chatBox"></div>
<input type="text" id="replyText" placeholder="输入回复,Enter发送...">
<button class="btn" onclick="sendReply()">发送</button>
<button class="btn red" onclick="closeSession()">结束会话</button>
</div>
</div>
<div class="card">
<h3>最近对话</h3>
<table><thead><tr><th>ID</th><th>标题</th><th>时间</th></tr></thead><tbody id="tbl"></tbody></table>
</div>
<a href="/">← 返回客服页</a>
<script>
let curSid = 0;
let lastId = 0;
const sessions = {}; // sid -> {lastId, msgs:[]}
fetch("/api/admin/stats").then(r=>r.json()).then(d=>{
s1.textContent=d.total_conv; s2.textContent=d.total_msg;
s3.textContent=d.today_conv; s4.textContent=d.good; s5.textContent=d.bad;
});
function loadQueue(){
fetch("/api/human/queue").then(r=>r.json()).then(list=>{
document.getElementById("queue").innerHTML = list.length
? list.map(x=>`<button class="btn" onclick="accept(${x.id})">#${x.id} ${x.ctime}</button>`).join(" ")
: "暂无";
});
}
loadQueue(); setInterval(loadQueue, 3000);
// 自动接入等待的客户
setInterval(()=>{
fetch("/api/human/queue").then(r=>r.json()).then(list=>{
if(list.length && !sessions[list[0].id]){
accept(list[0].id);
}
});
}, 3000);
function accept(sid){
fetch("/api/human/accept",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid})}).then(()=>{
sessions[sid] = {lastId:0};
switchSession(sid);
});
}
function switchSession(sid){
curSid = sid;
lastId = sessions[sid].lastId;
document.getElementById("chatCard").style.display="block";
document.getElementById("chatTitle").textContent="对话 #"+sid;
// 渲染已有消息
document.getElementById("chatBox").innerHTML = "";
sessions[sid].msgs.forEach(m=>{
const div=document.createElement("div");
div.className="msg "+(m.side==="customer"?"cust":"agent");
div.textContent=(m.side==="customer"?"客户: ":"客服: ")+m.content;
document.getElementById("chatBox").appendChild(div);
});
renderSessionList();
}
function renderSessionList(){
const el=document.getElementById("sessionList");
el.innerHTML="";
Object.keys(sessions).forEach(sid=>{
const div=document.createElement("div");
div.className="session-item"+(sid==curSid?" active":"");
div.textContent="对话 #"+sid;
div.onclick=()=>switchSession(sid);
el.appendChild(div);
});
}
function poll(){
Object.keys(sessions).forEach(sid=>{
fetch("/api/human/poll?sid="+sid+"&after="+sessions[sid].lastId).then(r=>r.json()).then(d=>{
d.messages.forEach(m=>{
sessions[sid].lastId=m.id;
sessions[sid].msgs.push(m);
if(sid==curSid){
const div=document.createElement("div");
div.className="msg "+(m.side==="customer"?"cust":"agent");
div.textContent=(m.side==="customer"?"客户: ":"客服: ")+m.content;
document.getElementById("chatBox").appendChild(div);
document.getElementById("chatBox").scrollTop=999999;
}
});
});
});
setTimeout(poll, 2000);
}
poll();
function sendReply(){
const t=document.getElementById("replyText").value.trim();
if(!t || !curSid) return;
fetch("/api/human/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:curSid,side:"agent",content:t})});
document.getElementById("replyText").value="";
}
function closeSession(){
fetch("/api/human/close",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:curSid})});
delete sessions[curSid];
curSid=0;
document.getElementById("chatCard").style.display="none";
renderSessionList();
}
document.getElementById("replyText").addEventListener("keydown", function(e){
if(e.key==="Enter"){ e.preventDefault(); sendReply(); }
});
fetch("/api/get_history").then(r=>r.json()).then(list=>{
const tb=document.getElementById("tbl");
list.slice(0,20).forEach(c=>{
tb.innerHTML+=`<tr><td>${c.id}</td><td>${c.title}</td><td>${c.utime||c.ctime}</td></tr>`;
});
});
</script>
</body>
</html>
+2468
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
hello
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

+8
View File
@@ -0,0 +1,8 @@
@echo off
cd /d D:\1\wuliu_chat
set DEEPSEEK_API_KEY=sk-529a6318230641a2b45bc28a716a1697
echo 柏悦物流客服系统已启动
echo 关闭这个黑窗口就停止服务
echo 浏览器打开 http://127.0.0.1:5000/
python am.py
pause