feat(addon/aikefu): 新增AI智能客服插件
This commit is contained in:
236
src/addon/aikefu/api/controller/Kefu.php
Normal file
236
src/addon/aikefu/api/controller/Kefu.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\api\controller;
|
||||
|
||||
use addon\aikefu\model\Config as KefuConfigModel;
|
||||
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||
use addon\aikefu\model\Message as KefuMessageModel;
|
||||
use app\api\controller\BaseApi;
|
||||
use extend\api\HttpClient;
|
||||
|
||||
class Kefu extends BaseApi
|
||||
{
|
||||
/**
|
||||
* 智能客服聊天接口
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function chat()
|
||||
{
|
||||
// 获取请求参数
|
||||
$message = $this->params['message'] ?? '';
|
||||
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||
$conversation_id = $this->params['conversation_id'] ?? '';
|
||||
$stream = $this->params['stream'] ?? false;
|
||||
|
||||
// 验证参数
|
||||
if (empty($message)) {
|
||||
return $this->response($this->error('请输入消息内容'));
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取智能客服配置
|
||||
$kefu_config_model = new KefuConfigModel();
|
||||
$config_info = $kefu_config_model->getConfig($this->site_id);
|
||||
|
||||
if (empty($config_info['data']['value']) || $config_info['data']['value']['status'] != 1) {
|
||||
return $this->response($this->error('智能客服暂未启用'));
|
||||
}
|
||||
|
||||
$config = $config_info['data']['value'];
|
||||
$apiKey = $config['api_key'];
|
||||
$baseUrl = $config['base_url'];
|
||||
$chatEndpoint = $config['chat_endpoint'];
|
||||
|
||||
// 构建请求数据
|
||||
$requestData = [
|
||||
'inputs' => [],
|
||||
'query' => $message,
|
||||
'response_mode' => $stream ? 'streaming' : 'blocking',
|
||||
'user' => $user_id,
|
||||
];
|
||||
|
||||
// 如果有会话ID,添加到请求中
|
||||
if (!empty($conversation_id)) {
|
||||
$requestData['conversation_id'] = $conversation_id;
|
||||
}
|
||||
|
||||
// 构建请求头
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
// 发送请求到Dify API
|
||||
$url = $baseUrl . $chatEndpoint;
|
||||
$response = HttpClient::http($url, 'POST', json_encode($requestData), $headers);
|
||||
|
||||
// 解析响应
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return $this->response($this->error('解析响应失败'));
|
||||
}
|
||||
|
||||
// 保存消息记录
|
||||
$kefu_message_model = new KefuMessageModel();
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
|
||||
// 保存用户消息
|
||||
$kefu_message_model->addMessage([
|
||||
'site_id' => $this->site_id,
|
||||
'user_id' => $user_id,
|
||||
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
|
||||
'message_id' => $result['message_id'] ?? '',
|
||||
'role' => 'user',
|
||||
'content' => $message,
|
||||
]);
|
||||
|
||||
// 保存机器人回复
|
||||
$kefu_message_model->addMessage([
|
||||
'site_id' => $this->site_id,
|
||||
'user_id' => $user_id,
|
||||
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
|
||||
'message_id' => $result['id'] ?? '',
|
||||
'role' => 'assistant',
|
||||
'content' => $result['answer'] ?? '',
|
||||
]);
|
||||
|
||||
// 更新会话状态或创建新会话
|
||||
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||
['site_id', '=', $this->site_id],
|
||||
['conversation_id', '=', $result['conversation_id'] ?? $conversation_id],
|
||||
]);
|
||||
|
||||
if (empty($conversation_info['data'])) {
|
||||
// 创建新会话
|
||||
$kefu_conversation_model->addConversation([
|
||||
'site_id' => $this->site_id,
|
||||
'user_id' => $user_id,
|
||||
'conversation_id' => $result['conversation_id'] ?? '',
|
||||
'name' => '智能客服会话',
|
||||
]);
|
||||
} else {
|
||||
// 更新会话状态
|
||||
$kefu_conversation_model->updateConversation([
|
||||
'status' => 1,
|
||||
], [
|
||||
['id', '=', $conversation_info['data']['id']],
|
||||
]);
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
return $this->response($this->success([
|
||||
'conversation_id' => $result['conversation_id'] ?? '',
|
||||
'reply' => $result['answer'] ?? '',
|
||||
'message_id' => $result['message_id'] ?? '',
|
||||
'finish_reason' => $result['finish_reason'] ?? '',
|
||||
'usage' => $result['usage'] ?? [],
|
||||
]));
|
||||
} catch (\Exception $e) {
|
||||
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话历史
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getHistory()
|
||||
{
|
||||
// 获取请求参数
|
||||
$conversation_id = $this->params['conversation_id'] ?? '';
|
||||
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||
$limit = $this->params['limit'] ?? 20;
|
||||
$offset = $this->params['offset'] ?? 0;
|
||||
|
||||
// 验证参数
|
||||
if (empty($conversation_id)) {
|
||||
return $this->response($this->error('会话ID不能为空'));
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取会话历史记录
|
||||
$kefu_message_model = new KefuMessageModel();
|
||||
$message_list = $kefu_message_model->getMessageList([
|
||||
['site_id', '=', $this->site_id],
|
||||
['user_id', '=', $user_id],
|
||||
['conversation_id', '=', $conversation_id],
|
||||
], 'id, role, content, create_time', 'create_time asc', $limit, $offset);
|
||||
|
||||
// 返回成功响应
|
||||
return $this->response($this->success([
|
||||
'messages' => $message_list['data'] ?? [],
|
||||
'total' => $message_list['total'] ?? 0,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
]));
|
||||
} catch (\Exception $e) {
|
||||
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createConversation()
|
||||
{
|
||||
// 获取请求参数
|
||||
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||
|
||||
try {
|
||||
// 获取智能客服配置
|
||||
$kefu_config_model = new KefuConfigModel();
|
||||
$config_info = $kefu_config_model->getConfig($this->site_id);
|
||||
|
||||
if (empty($config_info['data']['value']) || $config_info['data']['value']['status'] != 1) {
|
||||
return $this->response($this->error('智能客服暂未启用'));
|
||||
}
|
||||
|
||||
$config = $config_info['data']['value'];
|
||||
$apiKey = $config['api_key'];
|
||||
$baseUrl = $config['base_url'];
|
||||
|
||||
// 构建请求数据
|
||||
$requestData = [
|
||||
'name' => '智能客服会话',
|
||||
'user' => $user_id,
|
||||
];
|
||||
|
||||
// 构建请求头
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
// 发送请求到Dify API
|
||||
$url = $baseUrl . '/conversations';
|
||||
$response = HttpClient::http($url, 'POST', json_encode($requestData), $headers);
|
||||
|
||||
// 解析响应
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return $this->response($this->error('解析响应失败'));
|
||||
}
|
||||
|
||||
// 保存会话记录
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
$kefu_conversation_model->addConversation([
|
||||
'site_id' => $this->site_id,
|
||||
'user_id' => $user_id,
|
||||
'conversation_id' => $result['id'] ?? '',
|
||||
'name' => $result['name'] ?? '智能客服会话',
|
||||
]);
|
||||
|
||||
// 返回成功响应
|
||||
return $this->response($this->success([
|
||||
'conversation_id' => $result['id'] ?? '',
|
||||
'name' => $result['name'] ?? '',
|
||||
'created_at' => $result['created_at'] ?? '',
|
||||
]));
|
||||
} catch (\Exception $e) {
|
||||
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/addon/aikefu/config/info.php
Normal file
15
src/addon/aikefu/config/info.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'aikefu',
|
||||
'title' => '智能客服',
|
||||
'description' => '基于Dify的智能客服系统',
|
||||
'author' => 'admin',
|
||||
'version' => '1.0.0',
|
||||
'scene' => 'web',
|
||||
'state' => 0,
|
||||
'category' => 'business',
|
||||
'need_install' => 1,
|
||||
'need_cache' => 1,
|
||||
'hooks' => [],
|
||||
];
|
||||
36
src/addon/aikefu/data/install.sql
Normal file
36
src/addon/aikefu/data/install.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- 智能客服插件安装脚本
|
||||
-- 1. 智能客服插件使用系统配置表存储配置信息,无需创建独立数据表
|
||||
-- 2. 会话和消息数据存储在独立数据表中
|
||||
|
||||
-- 创建智能客服会话表
|
||||
CREATE TABLE IF NOT EXISTS `lucky_aikefu_conversation` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`site_id` int(11) NOT NULL COMMENT '站点ID',
|
||||
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
|
||||
`conversation_id` varchar(100) NOT NULL COMMENT 'Dify会话ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '会话名称',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:1活跃,0结束',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `site_id` (`site_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `conversation_id` (`conversation_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能客服会话表';
|
||||
|
||||
-- 创建智能客服消息表
|
||||
CREATE TABLE IF NOT EXISTS `lucky_aikefu_message` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`site_id` int(11) NOT NULL COMMENT '站点ID',
|
||||
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
|
||||
`conversation_id` varchar(100) NOT NULL COMMENT '会话ID',
|
||||
`message_id` varchar(100) NOT NULL COMMENT '消息ID',
|
||||
`role` varchar(20) NOT NULL COMMENT '角色:user用户,assistant助手',
|
||||
`content` text NOT NULL COMMENT '消息内容',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `site_id` (`site_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `conversation_id` (`conversation_id`),
|
||||
KEY `message_id` (`message_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能客服消息表';
|
||||
4
src/addon/aikefu/data/uninstall.sql
Normal file
4
src/addon/aikefu/data/uninstall.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- 智能客服插件卸载脚本
|
||||
-- 删除智能客服相关表(配置信息存储在系统配置表中,无需单独删除)
|
||||
DROP TABLE IF EXISTS `lucky_aikefu_message`;
|
||||
DROP TABLE IF EXISTS `lucky_aikefu_conversation`;
|
||||
50
src/addon/aikefu/event/Install.php
Normal file
50
src/addon/aikefu/event/Install.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\event;
|
||||
|
||||
use app\model\system\Addon as AddonModel;
|
||||
|
||||
/**
|
||||
* 智能客服插件安装
|
||||
*/
|
||||
class Install
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$addon_model = new AddonModel();
|
||||
$info = $addon_model->getAddonInfo(['name' => 'aikefu']);
|
||||
|
||||
if (empty($info['data'])) {
|
||||
// 插件未安装,执行安装逻辑
|
||||
$addon_model->addAddon([
|
||||
'name' => 'aikefu',
|
||||
'title' => '智能客服',
|
||||
'description' => '基于Dify的智能客服系统',
|
||||
'author' => 'admin',
|
||||
'version' => '1.0.0',
|
||||
'scene' => 'web',
|
||||
'state' => 0,
|
||||
'category' => 'business',
|
||||
'need_install' => 1,
|
||||
'need_cache' => 1,
|
||||
'create_time' => time(),
|
||||
'update_time' => time()
|
||||
]);
|
||||
} else {
|
||||
// 插件已存在,更新插件信息
|
||||
$addon_model->updateAddon([
|
||||
'title' => '智能客服',
|
||||
'description' => '基于Dify的智能客服系统',
|
||||
'author' => 'admin',
|
||||
'version' => '1.0.0',
|
||||
'scene' => 'web',
|
||||
'category' => 'business',
|
||||
'need_install' => 1,
|
||||
'need_cache' => 1,
|
||||
'update_time' => time()
|
||||
], ['name' => 'aikefu']);
|
||||
}
|
||||
|
||||
return success(1);
|
||||
}
|
||||
}
|
||||
129
src/addon/aikefu/event/Kefu.php
Normal file
129
src/addon/aikefu/event/Kefu.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\event;
|
||||
|
||||
use addon\aikefu\api\controller\Kefu as KefuApi;
|
||||
|
||||
/**
|
||||
* 智能客服事件处理类
|
||||
*/
|
||||
class Kefu
|
||||
{
|
||||
/**
|
||||
* 处理智能客服聊天事件
|
||||
* @param array $data 事件数据
|
||||
* @return array
|
||||
*/
|
||||
public function handleKefuChat($data)
|
||||
{
|
||||
try {
|
||||
// 创建addon的KefuApi实例
|
||||
$kefu_api = new KefuApi();
|
||||
|
||||
// 设置必要的属性
|
||||
$kefu_api->site_id = $data['site_id'] ?? 0;
|
||||
$kefu_api->member_id = $data['member_id'] ?? 0;
|
||||
$kefu_api->token = $data['token'] ?? '';
|
||||
$kefu_api->params = [
|
||||
'message' => $data['message'] ?? '',
|
||||
'user_id' => $data['user_id'] ?? '',
|
||||
'conversation_id' => $data['conversation_id'] ?? '',
|
||||
'stream' => $data['stream'] ?? false,
|
||||
];
|
||||
|
||||
// 调用addon的chat方法
|
||||
$response = $kefu_api->chat();
|
||||
|
||||
// 返回响应数据
|
||||
return json_decode($response->getContent(), true);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'code' => -1,
|
||||
'message' => '聊天失败:' . $e->getMessage(),
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理智能客服创建会话事件
|
||||
* @param array $data 事件数据
|
||||
* @return array
|
||||
*/
|
||||
public function handleKefuCreateConversation($data)
|
||||
{
|
||||
try {
|
||||
// 创建addon的KefuApi实例
|
||||
$kefu_api = new KefuApi();
|
||||
|
||||
// 设置必要的属性
|
||||
$kefu_api->site_id = $data['site_id'] ?? 0;
|
||||
$kefu_api->member_id = $data['member_id'] ?? 0;
|
||||
$kefu_api->token = $data['token'] ?? '';
|
||||
$kefu_api->params = [
|
||||
'user_id' => $data['user_id'] ?? '',
|
||||
];
|
||||
|
||||
// 调用addon的createConversation方法
|
||||
$response = $kefu_api->createConversation();
|
||||
|
||||
// 返回响应数据
|
||||
return json_decode($response->getContent(), true);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'code' => -1,
|
||||
'message' => '创建会话失败:' . $e->getMessage(),
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理智能客服获取历史消息事件
|
||||
* @param array $data 事件数据
|
||||
* @return array
|
||||
*/
|
||||
public function handleKefuGetHistory($data)
|
||||
{
|
||||
try {
|
||||
// 创建addon的KefuApi实例
|
||||
$kefu_api = new KefuApi();
|
||||
|
||||
// 设置必要的属性
|
||||
$kefu_api->site_id = $data['site_id'] ?? 0;
|
||||
$kefu_api->member_id = $data['member_id'] ?? 0;
|
||||
$kefu_api->token = $data['token'] ?? '';
|
||||
$kefu_api->params = [
|
||||
'conversation_id' => $data['conversation_id'] ?? '',
|
||||
'user_id' => $data['user_id'] ?? '',
|
||||
'limit' => $data['limit'] ?? 20,
|
||||
'offset' => $data['offset'] ?? 0,
|
||||
];
|
||||
|
||||
// 调用addon的getHistory方法
|
||||
$response = $kefu_api->getHistory();
|
||||
|
||||
// 返回响应数据
|
||||
return json_decode($response->getContent(), true);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'code' => -1,
|
||||
'message' => '获取历史消息失败:' . $e->getMessage(),
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件监听映射
|
||||
* @return array
|
||||
*/
|
||||
public function subscribe()
|
||||
{
|
||||
return [
|
||||
'KefuChat' => 'handleKefuChat',
|
||||
'KefuCreateConversation' => 'handleKefuCreateConversation',
|
||||
'KefuGetHistory' => 'handleKefuGetHistory',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
src/addon/aikefu/event/UnInstall.php
Normal file
20
src/addon/aikefu/event/UnInstall.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\event;
|
||||
|
||||
use app\model\system\Addon as AddonModel;
|
||||
|
||||
/**
|
||||
* 智能客服插件卸载
|
||||
*/
|
||||
class UnInstall
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$addon_model = new AddonModel();
|
||||
// 删除插件信息
|
||||
$addon_model->deleteAddon(['name' => 'aikefu']);
|
||||
|
||||
return success(1);
|
||||
}
|
||||
}
|
||||
1
src/addon/aikefu/icon.png
Normal file
1
src/addon/aikefu/icon.png
Normal file
File diff suppressed because one or more lines are too long
53
src/addon/aikefu/model/Config.php
Normal file
53
src/addon/aikefu/model/Config.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\model;
|
||||
|
||||
use app\model\system\Config as ConfigModel;
|
||||
use app\model\BaseModel;
|
||||
|
||||
/**
|
||||
* 智能客服配置
|
||||
*/
|
||||
class Config extends BaseModel
|
||||
{
|
||||
/**
|
||||
* 设置智能客服配置
|
||||
* @param array $data
|
||||
* @param int $site_id
|
||||
* @param string $app_module
|
||||
* @return array
|
||||
*/
|
||||
public function setConfig($data, $site_id = 0, $app_module = 'shop')
|
||||
{
|
||||
$config = new ConfigModel();
|
||||
$res = $config->setConfig($data, '智能客服配置', 1, [['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能客服配置
|
||||
* @param int $site_id
|
||||
* @param string $app_module
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig($site_id = 0, $app_module = 'shop')
|
||||
{
|
||||
$config = new ConfigModel();
|
||||
$res = $config->getConfig([['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能客服配置信息
|
||||
* @param array $condition
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigInfo($condition = [], $field = '*')
|
||||
{
|
||||
// 兼容旧的调用方式
|
||||
$site_id = $condition[0][1] ?? 0;
|
||||
$res = $this->getConfig($site_id);
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
102
src/addon/aikefu/model/Conversation.php
Normal file
102
src/addon/aikefu/model/Conversation.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\model;
|
||||
|
||||
use app\model\BaseModel;
|
||||
|
||||
class Conversation extends BaseModel
|
||||
{
|
||||
/**
|
||||
* 表名
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'aikefu_conversation';
|
||||
|
||||
/**
|
||||
* 主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 获取会话信息
|
||||
* @param array $condition
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getConversationInfo($condition = [], $field = '*')
|
||||
{
|
||||
$info = $this->where($condition)->field($field)->find();
|
||||
return empty($info) ? [] : $info->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @param array $condition
|
||||
* @param string $field
|
||||
* @param string $order
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getConversationList($condition = [], $field = '*', $order = 'id desc', $page = 1, $limit = 10)
|
||||
{
|
||||
$list = $this->where($condition)->field($field)->order($order)->paginate([
|
||||
'page' => $page,
|
||||
'list_rows' => $limit
|
||||
]);
|
||||
return $this->pageFormat($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加会话
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function addConversation($data)
|
||||
{
|
||||
$result = $this->insert($data);
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话
|
||||
* @param array $data
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public function updateConversation($data, $condition)
|
||||
{
|
||||
$result = $this->where($condition)->update($data);
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public function deleteConversation($condition)
|
||||
{
|
||||
$result = $this->where($condition)->delete();
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户会话列表
|
||||
* @param int $site_id
|
||||
* @param string $user_id
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getUserConversationList($site_id, $user_id, $page = 1, $limit = 10)
|
||||
{
|
||||
$condition = [
|
||||
['site_id', '=', $site_id],
|
||||
['user_id', '=', $user_id]
|
||||
];
|
||||
|
||||
return $this->getConversationList($condition, '*', 'update_time desc', $page, $limit);
|
||||
}
|
||||
}
|
||||
118
src/addon/aikefu/model/Message.php
Normal file
118
src/addon/aikefu/model/Message.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\model;
|
||||
|
||||
use app\model\BaseModel;
|
||||
|
||||
class Message extends BaseModel
|
||||
{
|
||||
/**
|
||||
* 表名
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'aikefu_message';
|
||||
|
||||
/**
|
||||
* 主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 获取消息信息
|
||||
* @param array $condition
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getMessageInfo($condition = [], $field = '*')
|
||||
{
|
||||
$info = $this->where($condition)->field($field)->find();
|
||||
return empty($info) ? [] : $info->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param array $condition
|
||||
* @param string $field
|
||||
* @param string $order
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getMessageList($condition = [], $field = '*', $order = 'id asc', $page = 1, $limit = 20)
|
||||
{
|
||||
$list = $this->where($condition)->field($field)->order($order)->paginate([
|
||||
'page' => $page,
|
||||
'list_rows' => $limit
|
||||
]);
|
||||
return $this->pageFormat($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function addMessage($data)
|
||||
{
|
||||
$result = $this->insert($data);
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息
|
||||
* @param array $data
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public function updateMessage($data, $condition)
|
||||
{
|
||||
$result = $this->where($condition)->update($data);
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public function deleteMessage($condition)
|
||||
{
|
||||
$result = $this->where($condition)->delete();
|
||||
return $this->success(['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话消息记录
|
||||
* @param int $site_id
|
||||
* @param string $conversation_id
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return array
|
||||
*/
|
||||
public function getConversationMessages($site_id, $conversation_id, $limit = 20, $offset = 0)
|
||||
{
|
||||
$condition = [
|
||||
['site_id', '=', $site_id],
|
||||
['conversation_id', '=', $conversation_id]
|
||||
];
|
||||
|
||||
return $this->getMessageList($condition, '*', 'create_time asc', ($offset / $limit) + 1, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户消息总数
|
||||
* @param int $site_id
|
||||
* @param string $user_id
|
||||
* @return array
|
||||
*/
|
||||
public function getUserMessageCount($site_id, $user_id)
|
||||
{
|
||||
$count = $this->where([
|
||||
['site_id', '=', $site_id],
|
||||
['user_id', '=', $user_id]
|
||||
])->count();
|
||||
|
||||
return $this->success(['count' => $count]);
|
||||
}
|
||||
}
|
||||
222
src/addon/aikefu/shop/controller/Kefu.php
Normal file
222
src/addon/aikefu/shop/controller/Kefu.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace addon\aikefu\shop\controller;
|
||||
|
||||
use addon\aikefu\model\Config as KefuConfigModel;
|
||||
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||
use addon\aikefu\model\Message as KefuMessageModel;
|
||||
use app\shop\controller\BaseShop;
|
||||
use think\facade\View;
|
||||
|
||||
class Kefu extends BaseShop
|
||||
{
|
||||
/**
|
||||
* 智能客服配置页
|
||||
* @return \think\response\View
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
$kefu_config_model = new KefuConfigModel();
|
||||
$config_info = $kefu_config_model->getConfigInfo([['site_id', '=', $this->site_id]]);
|
||||
|
||||
View::assign('config_info', $config_info);
|
||||
return View::fetch('kefu/config');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存智能客服配置
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function saveConfig()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
|
||||
$kefu_config_model = new KefuConfigModel();
|
||||
|
||||
$data = [
|
||||
'api_key' => $params['api_key'] ?? '',
|
||||
'base_url' => $params['base_url'] ?? 'https://api.dify.ai/v1',
|
||||
'chat_endpoint' => $params['chat_endpoint'] ?? '/chat-messages',
|
||||
'status' => $params['status'] ?? 0,
|
||||
];
|
||||
|
||||
$result = $kefu_config_model->setConfig($data, $this->site_id);
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话管理列表
|
||||
* @return \think\response\View
|
||||
*/
|
||||
public function conversation()
|
||||
{
|
||||
return View::fetch('kefu/conversation');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getConversationList()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$page = $params['page'] ?? 1;
|
||||
$limit = $params['limit'] ?? 10;
|
||||
$user_id = $params['user_id'] ?? '';
|
||||
$status = $params['status'] ?? '';
|
||||
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
$condition = [['site_id', '=', $this->site_id]];
|
||||
|
||||
if (!empty($user_id)) {
|
||||
$condition[] = ['user_id', '=', $user_id];
|
||||
}
|
||||
|
||||
if ($status !== '') {
|
||||
$condition[] = ['status', '=', $status];
|
||||
}
|
||||
|
||||
$conversation_list = $kefu_conversation_model->getConversationList($condition, '*', 'update_time desc', $page, $limit);
|
||||
|
||||
return $this->success($conversation_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getConversationInfo()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$conversation_id = $params['conversation_id'] ?? '';
|
||||
|
||||
if (empty($conversation_id)) {
|
||||
return $this->error('会话ID不能为空');
|
||||
}
|
||||
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||
['site_id', '=', $this->site_id],
|
||||
['conversation_id', '=', $conversation_id]
|
||||
]);
|
||||
|
||||
if (empty($conversation_info)) {
|
||||
return $this->error('会话不存在');
|
||||
}
|
||||
|
||||
return $this->success($conversation_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束会话
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function endConversation()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$id = $params['id'] ?? '';
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->error('会话ID不能为空');
|
||||
}
|
||||
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
$result = $kefu_conversation_model->updateConversation(
|
||||
['status' => 0],
|
||||
[
|
||||
['id', '=', $id],
|
||||
['site_id', '=', $this->site_id]
|
||||
]
|
||||
);
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteConversation()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$id = $params['id'] ?? '';
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->error('会话ID不能为空');
|
||||
}
|
||||
|
||||
$kefu_conversation_model = new KefuConversationModel();
|
||||
$kefu_message_model = new KefuMessageModel();
|
||||
|
||||
// 开启事务
|
||||
$this->model->startTrans();
|
||||
|
||||
try {
|
||||
// 删除会话关联的消息
|
||||
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||
['id', '=', $id],
|
||||
['site_id', '=', $this->site_id]
|
||||
]);
|
||||
|
||||
if (!empty($conversation_info)) {
|
||||
$kefu_message_model->deleteMessage([
|
||||
['site_id', '=', $this->site_id],
|
||||
['conversation_id', '=', $conversation_info['conversation_id']]
|
||||
]);
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
$result = $kefu_conversation_model->deleteConversation([
|
||||
['id', '=', $id],
|
||||
['site_id', '=', $this->site_id]
|
||||
]);
|
||||
|
||||
// 提交事务
|
||||
$this->model->commit();
|
||||
|
||||
return $this->success($result);
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息管理列表
|
||||
* @return \think\response\View
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
$conversation_id = $this->request->param('conversation_id') ?? '';
|
||||
View::assign('conversation_id', $conversation_id);
|
||||
return View::fetch('kefu/message');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMessageList()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$page = $params['page'] ?? 1;
|
||||
$limit = $params['limit'] ?? 50;
|
||||
$conversation_id = $params['conversation_id'] ?? '';
|
||||
|
||||
if (empty($conversation_id)) {
|
||||
return $this->error('会话ID不能为空');
|
||||
}
|
||||
|
||||
$kefu_message_model = new KefuMessageModel();
|
||||
$condition = [
|
||||
['site_id', '=', $this->site_id],
|
||||
['conversation_id', '=', $conversation_id]
|
||||
];
|
||||
|
||||
$message_list = $kefu_message_model->getMessageList($condition, '*', 'create_time asc', $page, $limit);
|
||||
|
||||
return $this->success($message_list);
|
||||
}
|
||||
}
|
||||
98
src/addon/aikefu/shop/view/kefu/config.html
Normal file
98
src/addon/aikefu/shop/view/kefu/config.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>智能客服配置</title>
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/form.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="container">
|
||||
<div class="page-title">
|
||||
<h2>智能客服配置</h2>
|
||||
</div>
|
||||
<div class="card">
|
||||
<form id="kefuConfigForm">
|
||||
<div class="form-group">
|
||||
<label for="api_key">Dify API密钥</label>
|
||||
<input type="text" name="api_key" id="api_key" value="{$config_info.api_key ?? ''}" placeholder="请输入Dify API密钥">
|
||||
<div class="form-tips">
|
||||
从Dify平台获取的API密钥,用于调用Dify聊天机器人API。
|
||||
<a href="https://dify.ai/" target="_blank">前往Dify平台</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="base_url">API基础地址</label>
|
||||
<input type="text" name="base_url" id="base_url" value="{$config_info.base_url ?? 'https://api.dify.ai/v1'}" placeholder="请输入Dify API基础地址">
|
||||
<div class="form-tips">
|
||||
Dify API的基础地址,默认为https://api.dify.ai/v1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="chat_endpoint">聊天接口端点</label>
|
||||
<input type="text" name="chat_endpoint" id="chat_endpoint" value="{$config_info.chat_endpoint ?? '/chat-messages'}" placeholder="请输入聊天接口端点">
|
||||
<div class="form-tips">
|
||||
聊天接口的端点,默认为/chat-messages
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="status">状态</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="status" value="1" {if isset($config_info.status) && $config_info.status == 1}checked{/if}>
|
||||
<span>启用</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="status" value="0" {if !isset($config_info.status) || $config_info.status == 0}checked{/if}>
|
||||
<span>禁用</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
启用或禁用智能客服功能
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">保存配置</button>
|
||||
<button type="reset" class="btn btn-default">重置</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
|
||||
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
$('#kefuConfigForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var formData = $(this).serialize();
|
||||
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/saveConfig',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
245
src/addon/aikefu/shop/view/kefu/conversation.html
Normal file
245
src/addon/aikefu/shop/view/kefu/conversation.html
Normal file
@@ -0,0 +1,245 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>会话管理</title>
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/table.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="container">
|
||||
<div class="page-title">
|
||||
<h2>会话管理</h2>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="search-box">
|
||||
<div class="search-item">
|
||||
<label for="user_id">用户ID</label>
|
||||
<input type="text" name="user_id" id="user_id" placeholder="请输入用户ID">
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<label for="status">状态</label>
|
||||
<select name="status" id="status">
|
||||
<option value="">全部</option>
|
||||
<option value="1">活跃</option>
|
||||
<option value="0">已结束</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<button type="button" class="btn btn-primary" id="searchBtn">搜索</button>
|
||||
<button type="button" class="btn btn-default" id="resetBtn">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>会话ID</th>
|
||||
<th>用户ID</th>
|
||||
<th>会话名称</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>更新时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="conversationList">
|
||||
<!-- 会话列表将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination" id="pagination">
|
||||
<!-- 分页将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
|
||||
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
// 分页参数
|
||||
var page = 1;
|
||||
var limit = 10;
|
||||
var total = 0;
|
||||
|
||||
// 加载会话列表
|
||||
function loadConversationList() {
|
||||
var user_id = $('#user_id').val();
|
||||
var status = $('#status').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/getConversationList',
|
||||
type: 'POST',
|
||||
data: {
|
||||
page: page,
|
||||
limit: limit,
|
||||
user_id: user_id,
|
||||
status: status
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
var list = res.data.list;
|
||||
total = res.data.total;
|
||||
|
||||
var html = '';
|
||||
if (list.length > 0) {
|
||||
list.forEach(function(item) {
|
||||
html += '<tr>';
|
||||
html += '<td>' + item.id + '</td>';
|
||||
html += '<td>' + item.conversation_id + '</td>';
|
||||
html += '<td>' + item.user_id + '</td>';
|
||||
html += '<td>' + item.name + '</td>';
|
||||
html += '<td>' + (item.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</td>';
|
||||
html += '<td>' + item.create_time + '</td>';
|
||||
html += '<td>' + item.update_time + '</td>';
|
||||
html += '<td>';
|
||||
html += '<a href="javascript:;" class="btn btn-small btn-primary" onclick="viewMessages(' + item.id + ', \'' + item.conversation_id + '\')">查看消息</a>';
|
||||
html += '<a href="javascript:;" class="btn btn-small btn-warning" onclick="endConversation(' + item.id + ')">结束会话</a>';
|
||||
html += '<a href="javascript:;" class="btn btn-small btn-danger" onclick="deleteConversation(' + item.id + ')">删除</a>';
|
||||
html += '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
} else {
|
||||
html += '<tr><td colspan="8" class="text-center">暂无会话数据</td></tr>';
|
||||
}
|
||||
|
||||
$('#conversationList').html(html);
|
||||
// 渲染分页
|
||||
renderPagination();
|
||||
} else {
|
||||
layer.msg('加载失败:' + res.message, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染分页
|
||||
function renderPagination() {
|
||||
var pages = Math.ceil(total / limit);
|
||||
var html = '';
|
||||
|
||||
if (pages > 1) {
|
||||
html += '<ul class="pagination-list">';
|
||||
|
||||
// 上一页
|
||||
if (page > 1) {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + (page - 1) + ')">上一页</a></li>';
|
||||
} else {
|
||||
html += '<li class="disabled"><a href="javascript:;">上一页</a></li>';
|
||||
}
|
||||
|
||||
// 页码
|
||||
for (var i = 1; i <= pages; i++) {
|
||||
if (i === page) {
|
||||
html += '<li class="active"><a href="javascript:;">' + i + '</a></li>';
|
||||
} else {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + i + ')">' + i + '</a></li>';
|
||||
}
|
||||
}
|
||||
|
||||
// 下一页
|
||||
if (page < pages) {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + (page + 1) + ')">下一页</a></li>';
|
||||
} else {
|
||||
html += '<li class="disabled"><a href="javascript:;">下一页</a></li>';
|
||||
}
|
||||
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
$('#pagination').html(html);
|
||||
}
|
||||
|
||||
// 切换页码
|
||||
window.changePage = function(p) {
|
||||
page = p;
|
||||
loadConversationList();
|
||||
}
|
||||
|
||||
// 查看消息
|
||||
window.viewMessages = function(id, conversation_id) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '消息记录',
|
||||
content: '/shop/aikefu/kefu/message?conversation_id=' + conversation_id,
|
||||
area: ['90%', '90%']
|
||||
});
|
||||
}
|
||||
|
||||
// 结束会话
|
||||
window.endConversation = function(id) {
|
||||
layer.confirm('确定要结束该会话吗?', function(index) {
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/endConversation',
|
||||
type: 'POST',
|
||||
data: {id: id},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('会话已结束', {icon: 1});
|
||||
loadConversationList();
|
||||
} else {
|
||||
layer.msg('操作失败:' + res.message, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
window.deleteConversation = function(id) {
|
||||
layer.confirm('确定要删除该会话吗?删除后将无法恢复', function(index) {
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/deleteConversation',
|
||||
type: 'POST',
|
||||
data: {id: id},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('会话已删除', {icon: 1});
|
||||
loadConversationList();
|
||||
} else {
|
||||
layer.msg('操作失败:' + res.message, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索
|
||||
$('#searchBtn').click(function() {
|
||||
page = 1;
|
||||
loadConversationList();
|
||||
});
|
||||
|
||||
// 重置
|
||||
$('#resetBtn').click(function() {
|
||||
$('#user_id').val('');
|
||||
$('#status').val('');
|
||||
page = 1;
|
||||
loadConversationList();
|
||||
});
|
||||
|
||||
// 初始化加载
|
||||
loadConversationList();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
276
src/addon/aikefu/shop/view/kefu/message.html
Normal file
276
src/addon/aikefu/shop/view/kefu/message.html
Normal file
@@ -0,0 +1,276 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>消息管理</title>
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
|
||||
<link rel="stylesheet" href="/__STATIC__/admin/css/table.css">
|
||||
<style>
|
||||
.message-list {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.message-item {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.message-item.user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.message-item.assistant {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.message-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.message-content {
|
||||
max-width: 70%;
|
||||
padding: 12px 16px;
|
||||
border-radius: 18px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.message-item.user .message-content {
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.message-item.assistant .message-content {
|
||||
background-color: white;
|
||||
color: #333;
|
||||
border-bottom-left-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.message-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.message-role {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.conversation-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.conversation-info h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
.conversation-info p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="container">
|
||||
<div class="page-title">
|
||||
<h2>消息管理</h2>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div id="conversationInfo" class="conversation-info">
|
||||
<!-- 会话信息将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<div class="search-item">
|
||||
<label for="conversation_id">会话ID</label>
|
||||
<input type="text" name="conversation_id" id="conversation_id" placeholder="请输入会话ID" value="{$conversation_id ?? ''}">
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<button type="button" class="btn btn-primary" id="searchBtn">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-list" id="messageList">
|
||||
<!-- 消息列表将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
|
||||
<div class="pagination" id="pagination">
|
||||
<!-- 分页将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
|
||||
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
// 分页参数
|
||||
var page = 1;
|
||||
var limit = 50;
|
||||
var total = 0;
|
||||
var conversation_id = $('#conversation_id').val();
|
||||
|
||||
// 加载会话信息
|
||||
function loadConversationInfo() {
|
||||
if (!conversation_id) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/getConversationInfo',
|
||||
type: 'POST',
|
||||
data: {
|
||||
conversation_id: conversation_id
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
var info = res.data;
|
||||
var html = '<h3>会话详情</h3>';
|
||||
html += '<p><strong>会话ID:</strong>' + info.conversation_id + '</p>';
|
||||
html += '<p><strong>用户ID:</strong>' + info.user_id + '</p>';
|
||||
html += '<p><strong>会话名称:</strong>' + info.name + '</p>';
|
||||
html += '<p><strong>状态:</strong>' + (info.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</p>';
|
||||
html += '<p><strong>创建时间:</strong>' + info.create_time + '</p>';
|
||||
html += '<p><strong>更新时间:</strong>' + info.update_time + '</p>';
|
||||
$('#conversationInfo').html(html);
|
||||
} else {
|
||||
$('#conversationInfo').html('<h3>会话详情</h3><p>未找到会话信息</p>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$('#conversationInfo').html('<h3>会话详情</h3><p>加载会话信息失败</p>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载消息列表
|
||||
function loadMessageList() {
|
||||
if (!conversation_id) {
|
||||
$('#messageList').html('<div style="text-align: center; padding: 50px;">请输入会话ID进行搜索</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/shop/aikefu/kefu/getMessageList',
|
||||
type: 'POST',
|
||||
data: {
|
||||
page: page,
|
||||
limit: limit,
|
||||
conversation_id: conversation_id
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
var list = res.data.list;
|
||||
total = res.data.total;
|
||||
|
||||
var html = '';
|
||||
if (list.length > 0) {
|
||||
list.forEach(function(item) {
|
||||
var role = item.role === 'user' ? '用户' : '机器人';
|
||||
var roleClass = item.role === 'user' ? 'user' : 'assistant';
|
||||
var avatar = item.role === 'user' ? '/__STATIC__/admin/img/user.png' : '/__STATIC__/admin/img/robot.png';
|
||||
|
||||
html += '<div class="message-item ' + roleClass + '">';
|
||||
if (item.role === 'assistant') {
|
||||
html += '<img src="' + avatar + '" class="message-avatar">';
|
||||
}
|
||||
html += '<div>';
|
||||
html += '<div class="message-role">' + role + '</div>';
|
||||
html += '<div class="message-content">' + item.content + '</div>';
|
||||
html += '<div class="message-time">' + item.create_time + '</div>';
|
||||
html += '</div>';
|
||||
if (item.role === 'user') {
|
||||
html += '<img src="' + avatar + '" class="message-avatar">';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
} else {
|
||||
html += '<div style="text-align: center; padding: 50px;">暂无消息记录</div>';
|
||||
}
|
||||
|
||||
$('#messageList').html(html);
|
||||
// 滚动到底部
|
||||
$('#messageList').scrollTop($('#messageList')[0].scrollHeight);
|
||||
// 渲染分页
|
||||
renderPagination();
|
||||
} else {
|
||||
layer.msg('加载失败:' + res.message, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染分页
|
||||
function renderPagination() {
|
||||
var pages = Math.ceil(total / limit);
|
||||
var html = '';
|
||||
|
||||
if (pages > 1) {
|
||||
html += '<ul class="pagination-list">';
|
||||
|
||||
// 上一页
|
||||
if (page > 1) {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + (page - 1) + ')">上一页</a></li>';
|
||||
} else {
|
||||
html += '<li class="disabled"><a href="javascript:;">上一页</a></li>';
|
||||
}
|
||||
|
||||
// 页码
|
||||
for (var i = 1; i <= pages; i++) {
|
||||
if (i === page) {
|
||||
html += '<li class="active"><a href="javascript:;">' + i + '</a></li>';
|
||||
} else {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + i + ')">' + i + '</a></li>';
|
||||
}
|
||||
}
|
||||
|
||||
// 下一页
|
||||
if (page < pages) {
|
||||
html += '<li><a href="javascript:;" onclick="changePage(' + (page + 1) + ')">下一页</a></li>';
|
||||
} else {
|
||||
html += '<li class="disabled"><a href="javascript:;">下一页</a></li>';
|
||||
}
|
||||
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
$('#pagination').html(html);
|
||||
}
|
||||
|
||||
// 切换页码
|
||||
window.changePage = function(p) {
|
||||
page = p;
|
||||
loadMessageList();
|
||||
}
|
||||
|
||||
// 搜索
|
||||
$('#searchBtn').click(function() {
|
||||
conversation_id = $('#conversation_id').val();
|
||||
page = 1;
|
||||
loadConversationInfo();
|
||||
loadMessageList();
|
||||
});
|
||||
|
||||
// 初始化加载
|
||||
if (conversation_id) {
|
||||
loadConversationInfo();
|
||||
loadMessageList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user