# 執行環境
1. 安裝 Nodejs [v18.14.1](https://nodejs.org/en/blog/release/v18.14.1)
2. Clone repository
```shell
git clone https://never-mind@dev.azure.com/never-mind/smart-chatbot-with-gpt/_git/smart-chatbot-with-gpt
```
3. Install package
```shell
npm install
```
4. Run
```shell
npm run dev
```
5. Run production
```shell
npm run build
npm run preview
```
### Build images
```shell
docker build -t vue-ai-chatbot .
docker run -p 3000:3000 -it --name vue-ai-chatbot vue-ai-chatbot
```
### 產品首頁
http://localhost:3000/introduction.html
[Azure 測試網址](https://customer-chatbot.azurewebsites.net/introduction.html)
### 後台登入頁
http://localhost:3000/enterprises/login
[Azure 測試網址](https://customer-chatbot.azurewebsites.net)
- 測試帳號
- 客服角色 帳/密
- employee1@test.com / 1234
- employee2@test.com / 1234
- 管理者 帳/密
- admin1@test.com / 1234
### API Swagger
http://220.134.67.237:8080/swagger/index.html
<hr />
# ChatBot API
| 版本 | 最後變更日 | 修改人 | 描述 |
| :--------- | --------------- | ------ | ----------------------------------------------------------- |
| Alpha v1.0 | 2023/3/3 23:25 | Felix | 初版 條列前端部份功能 API |
| - | 2023/3/5 22:10 | Felix | 初版 修改部份 API |
| - | 2023/3/7 20:10 | Felix | 變更文字描述 customer->guest |
| - | 2023/3/19 16:40 | Felix | 新增產品首頁範例, 新增回覆滿意度 API, 修改部份 api 回傳參數 |
| - | 2023/3/20 21:10 | Felix | 變動 Signalr api, 提供範本 |
| - | 2023/3/22 22:04 | Felix | 新增, 變動工單和顧客 api |
| - | 2023/3/26 11:48 | Felix | 新增, 訊息紀錄查詢所需 api |
| - | 2023/4/15 23:30 | Steve | 新增, 中止聊天室對話 api<br />POST /api/v1/chatroom/cancel<br /><br />設定工單標籤與優先權 api<br />POST /api/v1/workorder/tagpriority<br /><br />新增管理/客服人員 Api<br />POST /api/v1/employee/create |
|| 2023/04/24 | Steve | 新增前端顧客註冊API<br />POST /api/v1/guest/register |
| | 2023/05/02 | Steve | 新增工作量報表API<br />POST /api/v1/report/workload<br /><br />新增滿意度調查報表API<br />POST /api/v1/report/satisfaction<br /><br />新增查詢客服人員明細API<br />GET /api/v1/employee/detail<br /><br />新增編輯客服人員API<br />POST /api/v1/employee/edit |
| | 2023/05/6 | Steve | 新增工作量報表-儀錶板-每小時在線聊天室數量<br />GET /api/v1/report/workload/dashbord/hourofday<br /><br />新增工作量報表-儀錶板-平均一周在線聊天室數量<br />GET /api/v1/report/workload/dashbord/weekofmonth<br /><br />新增工作量報表-儀錶板-每個月/年在線聊天室數量<br />GET /api/v1/report/workload/dashbord/monthofyear<br /><br />新增工作量報表-儀錶板-top 10聊天類型種類(工單的標籤)區分 一個月資料<br />GET /api/v1/report/workload/dashbord/tag<br /><br />新增工作量報表-儀錶板-平均一天/周聊天優先等級區分 一個月資料<br />GET /api/v1/report/workload/dashbord/priority<br /><br />新增滿意度調查報表-儀錶板-當天工單分數<br />GET /api/v1/report/satisfaction/dashboard/hourofday<br /><br />新增滿意度調查報表-儀錶板-一周工單分數<br />GET /api/v1/report/satisfaction/dashboard/weekofmonth<br /><br />新增滿意度調查報表-儀錶板-每個月工單數<br />GET /api/v1/report/satisfaction/dashboard/monthofyear |
<hr/>
> ## 帳號相關
| 角色(roleId) | 描述 |
| ------------ | -------- |
| 0 | 顧客 |
| 1 | 客服 |
| 2 | 企業帳號 |
> ## SignalR訂閱/解訂閱 & Hub方法
Hub端點: /hubs/chat
| 事件 | 描述 |
| -------- | ------- |
| SubscribeChatroom(string roomId) | 訂閱聊天室 |
| UnsubscribeChatroom(string roomId) | 解訂閱聊天室 |
| SubscribeCustomerService() | 訂閱客服人員群組, 所有客服人員都要訂閱 |
| UnsubscribeCustomerService() | 解訂閱客服人員群組 |
| Join(JoinRequest request) | 加入聊天室, 參數"同聊天 Hub 端點" |
| Chat(ChatRequest request) | 訊息聊天, 參數"同聊天 Hub 端點" |
| Readed(ReadedRequest request) | 訊息被已讀, 參數"同聊天 Hub 端點" |
| Leave(JoinRequest request) | 離開聊天室, 參數"同聊天 Hub 端點" |
列舉對應文字
```C#
public enum MessageType
{
[Description("text")]
Text,
[Description("file")]
File,
//特殊命令, 參照CommandType
[Description("command")]
Command
}
public enum CommandType
{
//一般聊天
[Description("chat")]
Chat,
//完成對話
[Description("complete")]
Complete,
//取消對話
[Description("cancel")]
Cancel,
[Description("like")]
Like
}
```
### C#聊天室發話範例
```C#
internal class Program
{
private static HubConnection connection;
private static void Main(string[] args)
{
try
{
//連線設定
connection = new HubConnectionBuilder()
.WithUrl("http://220.134.67.237:8080/hubs/chat")
.Build();
//監聽事件
connection.On<ChatResponse>("ChatReceive", message =>
{
Console.WriteLine(JsonConvert.SerializeObject(message));
});
//建立SignalR連線
connection.StartAsync().Wait();
if (connection.State == HubConnectionState.Connected)
{
Console.WriteLine("連線成功!!");
}
var roomId = Guid.NewGuid().ToString();
var senderId = Guid.NewGuid().ToString();
//訂閱聊天室
connection.InvokeAsync("SubscribeChatroom", roomId);
//發送Chat事件
connection.InvokeAsync("Chat", new
{
RoomId = roomId,
SenderId = senderId,
Name = "Steve",
RoleId = 0,
Message = "Hello Alex",
MessageType = "text",
CommandType = "chat"
});
Console.Read();
//解訂閱
connection.InvokeAsync("UnsubscribeChatroom", roomId);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
```
### 報表
#### 工作量報表
POST /api/v1/report/workload
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"startDate": "2023-04-02", //can't null
"endDate": "2023-05-02", //can't null, 日期區間預設帶入1個月
"deptId": 1,
"employeeName": "阿夫", //模糊搜尋
"tag": "重要", //模糊搜尋
"priority": 2, //1:最優先, 2:緊急, 3:一般, 4:低, 5:最低
"like": 2 //1:差(1到3讚), 2:滿意(4到6讚), 3:非常滿意(7到10讚)
}
//Response
{
"status": "ok",
"message": "",
"data": [
{
"employeeId": "0b2bbd39-4992-493e-af0b-9d59b5b31f50",
"employeeName": "測試帳號3",
"online": true,
"averageFirstResponseTime": "6天1時17分10秒", //第一次平均回覆時間
"averageResponseTime": "6天1時17分10秒", //平均回覆時間
"maxResponseTime": "16天2時10分34秒", //最久回覆時間
"unprocess": 0, //未處理工單數
"processing": 4, //處理中工單數
"finished": 2, //完成工單數
"abandon": 0 //中止工單數
},
...
]
}
```
#### 工作量報表-儀錶板-每小時在線聊天室數量
GET /api/v1/report/workload/dashbord/hourofday
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "1",
"y": "0"
},
{
"x": "2",
"y": "0"
},
{
"x": "3",
"y": "0"
},
{
"x": "4",
"y": "0"
},
{
"x": "5",
"y": "0"
},
{
"x": "6",
"y": "0"
},
{
"x": "7",
"y": "0"
},
{
"x": "8",
"y": "0"
},
{
"x": "9",
"y": "0"
},
{
"x": "10",
"y": "0"
},
{
"x": "11",
"y": "0"
},
{
"x": "12",
"y": "0"
},
{
"x": "13",
"y": "0"
},
{
"x": "14",
"y": "0"
},
{
"x": "15",
"y": "0"
},
{
"x": "16",
"y": "0"
},
{
"x": "17",
"y": "0"
},
{
"x": "18",
"y": "0"
},
{
"x": "19",
"y": "0"
},
{
"x": "20",
"y": "0"
},
{
"x": "21",
"y": "0"
},
{
"x": "22",
"y": "0"
},
{
"x": "23",
"y": "0"
},
{
"x": "24",
"y": "0"
}
]
}
```
#### 工作量報表-儀錶板-平均一周在線聊天室數量
GET /api/v1/report/workload/dashbord/weekofmonth
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319",
"startDate": "2023-04-17", //一周的第一天是星期一
"endDate": "2023-04-23"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "2023-04-17",
"y": "0"
},
{
"x": "2023-04-18",
"y": "1"
},
{
"x": "2023-04-19",
"y": "6"
},
{
"x": "2023-04-20",
"y": "0"
},
{
"x": "2023-04-21",
"y": "0"
},
{
"x": "2023-04-22",
"y": "5"
},
{
"x": "2023-04-23",
"y": "0"
}
]
}
```
#### 工作量報表-儀錶板-每個月/年在線聊天室數量
GET /api/v1/report/workload/dashbord/monthofyear
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "1",
"y": "0"
},
{
"x": "2",
"y": "0"
},
{
"x": "3",
"y": "0"
},
{
"x": "4",
"y": "24"
},
{
"x": "5",
"y": "3"
},
{
"x": "6",
"y": "0"
},
{
"x": "7",
"y": "0"
},
{
"x": "8",
"y": "0"
},
{
"x": "9",
"y": "0"
},
{
"x": "10",
"y": "0"
},
{
"x": "11",
"y": "0"
},
{
"x": "12",
"y": "0"
}
]
}
```
#### 工作量報表-儀錶板-top 10聊天類型種類(工單的標籤)區分 一個月資料
GET /api/v1/report/workload/dashbord/tag
- [x] 完成
```json
//Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "奧客",
"y": "1"
},
{
"x": "重要客戶",
"y": "1"
},
{
"x": "ok,gg",
"y": "1"
},
{
"x": "ddd",
"y": "1"
},
{
"x": "6666",
"y": "1"
}
]
}
```
#### 工作量報表-儀錶板-平均一天/周聊天優先等級區分 一個月資料
GET /api/v1/report/workload/dashbord/priority
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "最優先",
"y": "1"
},
{
"x": "緊急",
"y": "4"
},
{
"x": "一般",
"y": "20"
},
{
"x": "低",
"y": "2"
},
{
"x": "最低",
"y": "0"
}
]
}
```
#### 滿意度調查報表
POST /api/v1/report/satisfaction
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"startDate": "2023-04-02", //can't null
"endDate": "2023-05-02", //can't null, 日期區間預設帶入1個月
"deptId": 1,
"employeeName": "阿夫", //模糊搜尋
"like": 2 //1:差(1到3讚), 2:滿意(4到6讚), 3:非常滿意(7到10讚)
}
//Response
{
"status": "ok",
"message": "",
"data": [
{
"employeeId": "0b2bbd39-4992-493e-af0b-9d59b5b31f50",
"employeeName": "測試帳號3",
"deptId": 1,
"issues": 2, //聊天總數
"points": 0, //總讚數
"percentage": 9.09, //佔整體聊天數百分比
"great": 0, //非常滿意聊天數
"good": 0, //滿意聊天數
"bad": 0 //待改善聊天數
},
...
]
}
```
#### 滿意度調查報表-儀錶板-當天工單分數
GET /api/v1/report/satisfaction/dashboard/hourofday
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "1",
"y": "0"
},
{
"x": "2",
"y": "0"
},
{
"x": "3",
"y": "0"
},
{
"x": "4",
"y": "0"
},
{
"x": "5",
"y": "0"
},
{
"x": "6",
"y": "0"
},
{
"x": "7",
"y": "0"
},
{
"x": "8",
"y": "0"
},
{
"x": "9",
"y": "0"
},
{
"x": "10",
"y": "0"
},
{
"x": "11",
"y": "0"
},
{
"x": "12",
"y": "0"
},
{
"x": "13",
"y": "0"
},
{
"x": "14",
"y": "0"
},
{
"x": "15",
"y": "0"
},
{
"x": "16",
"y": "0"
},
{
"x": "17",
"y": "0"
},
{
"x": "18",
"y": "0"
},
{
"x": "19",
"y": "0"
},
{
"x": "20",
"y": "0"
},
{
"x": "21",
"y": "0"
},
{
"x": "22",
"y": "0"
},
{
"x": "23",
"y": "0"
},
{
"x": "24",
"y": "0"
}
]
}
```
#### 滿意度調查報表-儀錶板-一周工單分數
GET /api/v1/report/satisfaction/dashboard/weekofmonth
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319",
"startDate": "2023-04-17", //一周的第一天是星期一
"endDate": "2023-04-23"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "2023-04-17",
"y": "0"
},
{
"x": "2023-04-18",
"y": "0"
},
{
"x": "2023-04-19",
"y": "19"
},
{
"x": "2023-04-20",
"y": "0"
},
{
"x": "2023-04-21",
"y": "0"
},
{
"x": "2023-04-22",
"y": "24"
},
{
"x": "2023-04-23",
"y": "0"
}
]
}
```
#### 滿意度調查報表-儀錶板-每個月工單數
GET /api/v1/report/satisfaction/dashboard/monthofyear
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
// Response
{
"status": "ok",
"message": "",
"data": [
{
"x": "1",
"y": "0"
},
{
"x": "2",
"y": "0"
},
{
"x": "3",
"y": "0"
},
{
"x": "4",
"y": "44"
},
{
"x": "5",
"y": "14"
},
{
"x": "6",
"y": "0"
},
{
"x": "7",
"y": "0"
},
{
"x": "8",
"y": "0"
},
{
"x": "9",
"y": "0"
},
{
"x": "10",
"y": "0"
},
{
"x": "11",
"y": "0"
},
{
"x": "12",
"y": "0"
}
]
}
```
### 客服 / 企業端登入
#### POST /api/v1/enterprise/authenticate
- [x] 完成
- 登入後Bearer token將存放user資訊, 包含roleId, name, dept, organizeId...
```json
// Request:
{
"account": "0910231995", // email or mobileNum
"password": "1qaz@WSX"
}
// Response:
// 登入成功
{
"status": "ok",
"message": "登入成功",
"user":{
"id": 1,
"roleId": 1, // 1: 客服角色, 2: 企業管理者角色
"name": "大雄",
"dept": "技術部",
"deptId": 2,
"organize": "良心企業股份有限公司",
"organizeId": 20
},
"loginDate": "2023-03-02T00:00:00Z"
"token": "xxx-xxx-xxx-xxx-xxx", // jwt 可以用來解析使用者資訊
"tokenExpiredDate": "2030-10-10T00:00:00Z"
}
// 登入失敗:帳號或密碼錯誤
{
"status": "failed",
"message": "帳號或密碼錯誤"
}
```
### 企業帳號註冊
#### POST /api/v1/enterprise/register
- [x] 完成
```json
// Request:
{
"email": "alex001212@gmail.com",
"mobileNum": "0910231995", // 非必填
"organize": "良心企業股份有限公司",
"password": "1qaz@WSX"
}
// Response:
// 註冊成功
{
"status": "ok",
"message": "註冊成功"
}
// 註冊失敗
{
"status": "failed",
"errorMsg": "註冊失敗"
}
```
### 忘記密碼
#### POST /api/v1/enterprise/forget
- [ ] 完成
```json
// Request:
{
"email": "alex001212@gmail.com"
}
// Response:
E-mail 透過連結導回前端
https://localhost:3000/enterprise/resetpwd?email=alex001212@gmail.com&resetTOP=50125
```
### 重設密碼
#### POST /api/v1/enterprise/resetpwd
- [x] 完成
```json
// Request:
{
"email": "alex001212@gmail.com",
"resetOTP": "50125"
"password": "1qaz@WSX"
}
```
> ## 顧客端聊天相關
>

### 取得可用的客服清單
#### GET /api/v1/employee/list
- [x] 完成
```json
// Response:
[
{
"employeeId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"employeeName": "Mr.J",
"avatar": "https://domain.com/assets/images/avater001.jpg",
"isOnline": true,
"lastTimeOnline": "2023-03-02T00:00:00Z" //最後上線的時間
}
]
```
### 取得最近的訊息 (最後一筆)
#### POST /api/v1/chatroom/recently
- [x] 完成
- 會先按下傳送訊息過才會顯示, 並實際傳送訊息後
```json
// Request:
{
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec"
}
// Response:
| 回傳參數 | 說明 |
| ------------ | ------------------------- |
| roomId | 聊天室id |
| employeeId | 承接訊息的客服 |
| employeeName | 承接訊息客服的名稱 |
| name | 發話人 |
| roleId | 角色 |
| avatar | 發話人頭像 |
| mType | 訊息類型(file,image,text) |
| messageId | 最後的訊息ID |
| message | 最後的訊息 |
| messageTime | 最後訊息時間 |
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"employee": {
"employeeId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"employeeName": "Mr.J",
"avatar":"https://domain.com/assets/images/avater001.jpg",
},
"last": {
"name": "Alex",
"roleId": "0",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"mType": "text",
"messageId": 10222,
"message": "xxx...",
"messageTime": "2023-03-02T00:00:00Z"
}
}
```
### 傳送訊息給我們 (產生聊天室)
#### POST /api/v1/chatroom/create
- [x] 完成
- 瀏覽器判斷 localStorage 沒有 uuidv4 時, 會產生一組隨機 uuid, 用來當作匿名聊天的 guestId
- 若舊訊息已存在, 則不必再產生新的聊天室 id
- 若工單已被按下完成, 則產生新的工單
```json
// Request:
{
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name":"xxx",
"email": "xxx@xxx.ccc",
"tel": "0912345678"
}
// Response:
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a"
}
```

### 載入聊天室的所有訊息
#### POST /api/v1/chatroom/messages
- [x] 完成
- 先前若有傳送過訊息才須載入
```json
// Request
Authorize Header: Bearer token
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a"
}
// Response
| 回傳參數 | 說明 |
| ----------- | --------------------------------- |
| messageId | 訊息Id |
| senderId | 發話人ID |
| name | 暱稱 |
| roleId | 角色 |
| avatar | 發話人頭像 |
| mType | 訊息類型(file,image,text,command) |
| message | 訊息 |
| messageTime | 訊息時間 |
| readed | 是否已讀 |
{
"messages": [
{
"messageId": 10351,
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"roleId": "0",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"mType": "text",
"message": "xxx...",
"messageTime": "2023-03-02T00:00:00Z",
"readed": true,
},
...
]
}
```
> ## 客服端聊天相關 (客服權限)

### 取得被分配聊天室清單
#### POST /api/v1/chatroom/mylist
- [x] 完成
- 就是客服與顧客的聊天對話紀錄
- 顯示最後的訊息
```json
// Request:
Authorize Header: Bearer token
// Response:
| 回傳參數 | 說明 |
| ----------- | --------------------------------- |
| roomId | 聊天室id |
| senderId | 發話人ID |
| name | 發話人 |
| roleId | 角色 |
| avatar | 發話人頭像 |
| mType | 訊息類型(file,image,text,command) |
| messageId | 最後的訊息ID |
| message | 最後的訊息 |
| unread | 未讀訊息數 |
| messageTime | 最後訊息時間 |
[
{
"workOrderId":1,
"tag":"abc",
"priority":3,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"guest": {
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex"
},
"last": {
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"roleId":"0",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"mType": "text",
"messageId": 10222,
"message": "xxx...",
"unread": 2,
"messageTime": "2023-03-02T00:00:00Z"
}
}
]
```
### 載入聊天室的所有訊息
- (同顧客聊天相關 - 載入聊天室的所有訊息)
### 結束聊天室對話
#### POST /api/v1/chatroom/end
- [x] 完成
- 紀錄為完成的工單
- 結束後會推送給顧客一則訊息 command:like, 要求填寫滿意度
```json
// Request
Authorize Header: Bearer token
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a"
}
// Response
{
"status": "ok",
"message": "成功"
}
```
### 中止聊天室對話
#### POST /api/v1/chatroom/cancel
- [x] 完成
- 紀錄為中止的工單
- 結束後會推送給顧客一則訊息 command:cancel, 要求填寫滿意度
```json
// Request
Authorize Header: Bearer token
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a"
}
// Response
{
"status": "ok",
"message": "成功"
}
```
> ## 聊天連線
### 聊天 Hub 端點
#### /ws/v1/hubs/chat
- SinganR 的 ConnectionId 要與 senderId 做綁定
- 參考備註 [1. C# hub 範例](#sample-1)
#### 加入聊天室 (WS)
- [x] 完成
```json
// JoinSend
// JoinSend
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec", // 傳送者id 依照roleId判斷是guestId或employeeId
"name": "Alex",
"roleId": 0
}
// JoinReceive (推給其他人)
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"roleId": 0
}
```
#### 訊息聊天 (WS)
- [x] 完成
```json
// ChatSend
// 1. 一般訊息
{
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"roleId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"mType": "text",
"message": "哈嘍!"
}
// 2. 一般訊息 (檔案) (需要搭配另一檔案上傳的API)
{
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"roleId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"messageType": "file",
"commandType": "chat"
"message": "xxxxx-xxxxx-xxxx.jpeg"
}
// 3. 指令-按讚 (由客服結束對話時發送)
{
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"roleId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"mType": "command",
"message": "is_like"
}
// ChatReceive (推給其他人)
{
"senderId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"roleId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"name": "Mr.J",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"messageId": 102051,
"mType": "text",
"message": "哈嘍!",
"messageTime": "2023-03-02T00:00:00Z"
}
```
#### 訊息被已讀 (WS)
- [x] 完成
```json
// ReadSend
{
"senderId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"roleId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"messageId": 102051
}
// ReadReceive (推給其他人)
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"messageId": 102051
}
```
#### 離開聊天室 (WS)
- [x] 完成
```json
// LeaveReceive (推給其他人)
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"senderId": 1,
"name": "Mr.J",
"roleId": 1
}
```
### 回覆滿意度
#### POST /api/v1/chatroom/comment
- [x] 完成
```json
// Request
{
"point": 2,
"comment": "客服人員態度很差"
}
// Response
{
"status": "ok",
"message": "完成"
}
```
> ### 檔案上傳的 API
### 檔案上傳
#### POST /api/v1/file/upload
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"fileName": "xxxx-xxxx-xxxx.jpg",
"fileBase64": "xaffxxxxxx=z-"
}
```
> ## 顧客管理相關
### 註冊顧客帳號
#### POST /api/v1/guest/register
- [x] 完成
```json
// Request
{
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "顧客1",
"eMail": "dddd@gmail.com",
"tel": "0933000123"
}
//Response
{
"status": "ok",
"message": "帳戶註冊完成"
}
```
### 取得顧客清單
#### GET /api/v1/guest
- [x] 完成
```json
// Request
Authorize Header: Bearer token
// Response
[
{
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex"
}
...
]
```
> ## 工單相關
### 聊天 Hub 端點
#### /ws/v1/hubs/chat
#### 新工單產生 (WS)
- [x] 完成
```json
// WorkOrderReceive (推給所有客服)
{
"workOrderId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"guestName": "Alex", // 顧客名稱
"message": "我需要訂位",
"messageTime": "2023-03-02T00:00:00Z"
}
```

### 取得工單統計
#### GET /api/v1/workorder/status
- [ ] 完成
- 邏輯
- roleId=1
- 待處理的工單數顯示所有的
- 處理中的工單數顯示自己承接的
- 處理完成工單數(24H)顯示自己完成的
- 中止的工單數顯示自己承接後中止的
- roleId=2 看到所有的統計
```json
// Request:
Authorize Header: Bearer token
// Response:
{
unprocess: 0, // 待處理的工單數
processing: 1, // 處理中的工單數
finished: 1, // 處理完成的工單數 (24h)
abandon: 0 // 中止的工單數
}
```
### 取得未被分配的工單
#### GET /api/v1/workorder/unassignment
- [x] 完成
```json
// Request:
Authorize Header: Bearer token
// Response:
[
{
"workOrderId": 1,
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex", // 顧客名稱
"mType": "text", // 最後的訊息
"message": "我需要訂位", // 最後的訊息
"messageTime": "2023-03-02T00:00:00Z",
"created": "2023-03-02T00:00:00Z"
}
...
]
```
### 承接工單
#### POST /api/v1/workorder/undertake/
- [x] 完成
- 接了工單之後, 會顯示於聊天列表(/chatroom/mylist)
```json
// Request:
Authorize Header: Bearer token
{
"workOrderId": 1
}
// Response:
{
"status": "ok",
"message": "接單成功",
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a"
}
```

### 所有工單清單
#### POST /api/v1/workorder/list
- [x] 完成
- 邏輯
- roleId=1 看到客服自己承接的工單
- roleId=2 看到所有的
```json
// Request
Authorize Header: Bearer token
{
"state": 1, // 篩選工單狀態 (0:未處理, 1:處理中, 2:已完成, 3:中止)
"guestName": "Alex", // 篩選顧客名稱 (需求者)
}
// Response
[
{
"id": 1,
"state": "assignment",
"tag": "",
"guestName": "Alex", // 顧客名稱 (需求者)
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"type": "?", // 工單類型 (未提供)
"priority": "1", // 優先等級 (固定回傳1)
"created": "2023-03-02T00:00:00Z", // 工單建立時間
"modified": "2023-03-02T00:00:00Z",
"reason": "", // 狀態原因
"employee":{
"employeeId": "91d2a971-8ea2-43ba-98b1-d8f979200019", // 工單承接人
"employeeName": "Mr. J"
}
}
]
```
### 設定工單標籤與優先權
#### POST /api/v1/workorder/tagpriority
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"workOrderId": 1, // can't null
"tag": "奧客",
"priority": 4, // 優先權 1:最優先, 2:緊急, 3:一般, 4:低, 5:最低 can't null
}
// Response
{
"status": "ok",
"message": "成功"
}
```
> ## 顧客管理相關
>
### 所有顧客清單
#### POST /api/v1/guest/list
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"name": "Alex", // 篩選名稱
"email": null,
"mobile": null
}
// Response
[
{
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"email": "alex00123bb@gmail.com",
"mobile": "0912323111",
"tag": "",
"workOrderStatus": "assignment",
"created": "2023-03-02T00:00:00Z",
"modified": "2023-03-02T00:00:00Z"
},
{
"guestId": "1118537f-e2ad-4023-900c-b3c0824456ec",
"name": "葉師父",
"email": "yayaya@gmail.com",
"mobile": "0933000111",
"tag": "",
"workOrderStatus": "assignment",
"created": "2023-03-03T00:00:00Z",
"modified": "2023-03-04T00:00:00Z"
},
...
]
```
> ## 客服管理相關
>
TODO
> ## ChatGPT 相關
>
TODO
> ## 部門相關
### 取得部門清單
#### GET /api/v1/dept/list
- [x] 完成
```json
// Response
[
{
deptId:1,
deptName: "客服部"
}
...
]
```
### 新增部門
#### POST /api/v1/dept/create
- [x] 完成
### 修改部門
#### POST /api/v1/dept/update
- [x] 完成
### 修改部門人員
#### POST /api/v1/dept/employee
- [x] 完成
```json
// Request
{
"deptId": 9,
"employeeIds": [
"7661e585-35dc-4787-baac-e2c3d3674319",
"d31b60f4-33bd-470b-b6f8-4df16ce833c1"
]
}
// Response
{
"status": "ok",
"message": "修改部門人員成功"
}
```
### 刪除部門
#### POST /api/v1/dept/delete
- [x] 完成
> ## 訊息紀錄查詢相關
>
### 取得客服人員清單
#### POST /api/v1/employee/dept
- [x] 完成
```json
// Request:
{
"deptId": 1 // can be null
}
// Response:
[
{
"employeeId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"employeeName": "客服1號",
"deptId": 1,
"avatar": "https://domain.com/assets/images/avater001.jpg",
"isOnline": true,
"lastTimeOnline": "2023-03-02T00:00:00Z" //最後上線的時間
}
]
```
### 取得客服人員所有訊息
#### POST /api/v1/employee/chatroomlist
- [x] 完成
```json
// Request:
{
"employeeId": "91d2a971-8ea2-43ba-98b1-d8f979200019",
"startDate": "2023-03-01T00:00:00Z", // can be null
"endDate": "2023-03-31T00:00:00Z" // can be null
}
// Response:
[
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"guest": {
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex"
},
"last": {
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"roleId":"0",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"mType": "text",
"messageId": 10222,
"message": "xxx...",
"unread": 2,
"messageTime": "2023-03-02T00:00:00Z"
}
}
]
```
### 新增管理/客服人員帳號
POST /api/v1/employee/chatroomlist
- [x] 完成
// Request:
```json
{
"deptId": 1,
"roleId": 1, //1:客服人員, 2:管理員
"name": "約瑟夫",
"email": "abc@gmail.com",
"tel":, "1234567890"
"password": "123456"
}
```
// Response:
```json
[
{
"roomId": "69727d6d-c8a4-455a-aa22-903d485e524a",
"guest": {
"guestId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex"
},
"last": {
"senderId": "8068537f-e2ad-4023-900c-b3c0824456ec",
"name": "Alex",
"roleId":"0",
"avatar":"https://domain.com/assets/images/avater001.jpg",
"mType": "text",
"messageId": 10222,
"message": "xxx...",
"unread": 2,
"messageTime": "2023-03-02T00:00:00Z"
}
}
]
```
#### 取得客服人員明細
GET /api/v1/employee/detail
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319"
}
//Response
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319",
"employeeName": "阿夫",
"roleId": 1,
"deptId": 1,
"eMail": "joepopxp@gmail.com",
"mobile": "0963052383",
"password": "123456",
"avatar": ""
}
```
#### 編輯客服人員
POST /api/v1/employee/edit
- [x] 完成
```json
// Request
Authorize Header: Bearer token
{
"employeeId": "7661e585-35dc-4787-baac-e2c3d3674319",
"deptId": 1,
"roleId": 1,
"name": "阿夫",
"eMail": "joepopxp@gmail.com",
"tel": "0963052777",
"password": "123456"
}
//Response
{
"status": "ok",
"message": "編輯客服人員帳號成功"
}
```
## 備註
##### sample-1
- ChatHub.cs
```csharp
using System.Text.Json;
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task JoinSend(string message)
{
var data = JsonSerializer.Deserialize<object>(message);
await Clients.Others.SendAsync("JoinReceive", data);
}
public async Task ChatSend(string message)
{
var httpCtx = Context.GetHttpContext();
Console.WriteLine(String.Format("Client {0} send message", Context.ConnectionId));
var data = JsonSerializer.Deserialize<object>(message);
await Clients.Others.SendAsync("ChatReceive", data);
}
public async Task ReadSend(string message)
{
var httpCtx = Context.GetHttpContext();
Console.WriteLine(String.Format("Client {0} send message", Context.ConnectionId));
var data = JsonSerializer.Deserialize<object>(message);
await Clients.Others.SendAsync("ReadReceive", data);
}
// 連線
public override Task OnConnectedAsync()
{
Console.WriteLine(String.Format("Client {0} connected", Context.ConnectionId));
return base.OnConnectedAsync();
}
// 離線
public override async Task OnDisconnectedAsync(Exception? exception)
{
Console.WriteLine(String.Format("Client {0} dis-connected", Context.ConnectionId));
await Clients.Others.SendAsync("LeaveReceive", null);
}
}
}
```