diff --git a/Server/application/cozeai/config/route.php b/Server/application/cozeai/config/route.php index a1cc8d97..ca6c7022 100755 --- a/Server/application/cozeai/config/route.php +++ b/Server/application/cozeai/config/route.php @@ -11,6 +11,14 @@ Route::group('v1/cozeai', function () { // 会话管理 Route::group('conversation', function () { Route::get('list', 'cozeai/ConversationController/list'); - Route::post('create', 'cozeai/ConversationController/create'); + Route::get('create', 'cozeai/ConversationController/create'); + Route::post('createChat', 'cozeai/ConversationController/createChat'); + Route::get('chatRetrieve', 'cozeai/ConversationController/chatRetrieve'); + Route::get('chatMessage','cozeai/ConversationController/chatMessage'); }); -}); \ No newline at end of file + + // 消息管理 + Route::group('message', function () { + Route::get('list', 'cozeai/MessageController/getMessages'); + }); +})->middleware(['jwt']); \ No newline at end of file diff --git a/Server/application/cozeai/controller/BaseController.php b/Server/application/cozeai/controller/BaseController.php index e7548867..4336ba19 100755 --- a/Server/application/cozeai/controller/BaseController.php +++ b/Server/application/cozeai/controller/BaseController.php @@ -25,50 +25,86 @@ class BaseController extends Controller // 设置请求头 $this->headers = [ - 'Authorization:Bearer '. $this->accessToken, - 'Content-Type:application/json' + 'Authorization: Bearer ' . $this->accessToken, + 'Content-Type: application/json' ]; } - /** - * 发送GET请求 - * @param string $url 请求地址 - * @param array $params 请求参数 - * @return array + + +/** + * CURL请求 + * + * @param $url 请求url地址 + * @param $method 请求方法 get post + * @param null $postfields post数据数组 + * @param array $headers 请求header信息 + * @param bool|false $debug 调试开启 默认false + * @return mixed */ - protected function get($url, $params = []) + protected function httpRequest($url, $method = "GET", $postfields = null, $headers = array(), $timeout = 30, $debug = false) { - try { - $client = new \GuzzleHttp\Client(); - $response = $client->get($this->apiUrl . $url, [ - 'headers' => $this->headers, - 'query' => $params - ]); - return json_decode($response->getBody()->getContents(), true); - } catch (\Exception $e) { - return ['error' => $e->getMessage()]; + $method = strtoupper($method); + $ci = curl_init(); + /* Curl settings */ + // curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); // 使用哪个版本 + curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0"); + curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */ + // curl_setopt($ci, CURLOPT_TIMEOUT, 7); /* 设置cURL允许执行的最长秒数 */ + curl_setopt($ci, CURLOPT_TIMEOUT, $timeout); /* 设置cURL允许执行的最长秒数 */ + curl_setopt($ci, CURLOPT_RETURNTRANSFER, true); + switch ($method) { + case "POST": + curl_setopt($ci, CURLOPT_POST, true); + if (!empty($postfields)) { + if (is_string($postfields) && preg_match('/^([\w\-]+=[\w\-]+(&[\w\-]+=[\w\-]+)*)$/', $postfields)) { + parse_str($postfields, $output); + $postfields = $output; + } + if (is_array($postfields)) { + $tmpdatastr = http_build_query($postfields); + } else { + $tmpdatastr = $postfields; + } + curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr); + } + break; + default: + curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */ + break; } + + $ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE; + curl_setopt($ci, CURLOPT_URL, $url); + if ($ssl) { + curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts + curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在 + // curl_setopt($ci, CURLOPT_SSLVERSION, 4); //因为之前的POODLE 病毒爆发,许多网站禁用了sslv3(nginx默认是禁用的,ssl_protocols 默认值为TLSv1 TLSv1.1 TLSv1.2;),最新使用sslv4 + } + //curl_setopt($ci, CURLOPT_HEADER, true); /*启用时会将头文件的信息作为数据流输出*/ + if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) { + curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1); + } + curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/ + curl_setopt($ci, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ci, CURLINFO_HEADER_OUT, true); + /*curl_setopt($ci, CURLOPT_COOKIE, $Cookiestr); * *COOKIE带过去** */ + $response = curl_exec($ci); + $requestinfo = curl_getinfo($ci); + $http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); + if ($debug) { + echo "=====post data======\r\n"; + var_dump($postfields); + echo "=====info===== \r\n"; + print_r($requestinfo); + echo "=====response=====\r\n"; + print_r($response); + } + curl_close($ci); + return $response; + //return array($http_code, $response,$requestinfo); } - /** - * 发送POST请求 - * @param string $url 请求地址 - * @param array $data 请求数据 - * @return array - */ - protected function post($url, $data = []) - { - try { - $client = new \GuzzleHttp\Client(); - $response = $client->post($this->apiUrl . $url, [ - 'headers' => $this->headers, - 'json' => $data - ]); - return json_decode($response->getBody()->getContents(), true); - } catch (\Exception $e) { - return ['error' => $e->getMessage()]; - } - } } \ No newline at end of file diff --git a/Server/application/cozeai/controller/ConversationController.php b/Server/application/cozeai/controller/ConversationController.php index 276556bc..6a04ffe1 100755 --- a/Server/application/cozeai/controller/ConversationController.php +++ b/Server/application/cozeai/controller/ConversationController.php @@ -3,12 +3,15 @@ namespace app\cozeai\controller; use app\cozeai\model\Conversation as ConversationModel; - +use app\cozeai\model\Message as MessageModel; +use think\facade\Env; /** * Coze AI 对话控制器 */ class ConversationController extends BaseController { + + /** * 保存对话数据到数据库 * @param array $conversation 对话数据 @@ -22,20 +25,26 @@ class ConversationController extends BaseController // 检查是否已存在 $exists = ConversationModel::where('conversation_id', $conversation['id'])->find(); - $meta_data = $conversation['meta_data'] ?? []; - - if (!$exists) { // 不存在则插入 - return ConversationModel::create([ + $data = [ 'conversation_id' => $conversation['id'], 'bot_id' => $bot_id, 'created_at' => $conversation['created_at'], - 'meta_data' => json_encode($meta_data), + 'meta_data' => $meta_data, 'create_time' => time(), 'update_time' => time() - ]); + ]; + + if(isset($meta_data['uid']) && !empty($meta_data['uid'])){ + $data['userId'] = $meta_data['uid']; + } + + if(isset($meta_data['companyId']) && !empty($meta_data['companyId'])){ + $data['companyId'] = $meta_data['companyId']; + } + return ConversationModel::create($data); } else { // 存在则更新 return $exists->save([ @@ -87,12 +96,12 @@ class ConversationController extends BaseController } /** - * 创建对话 + * 创建会话 */ - public function create() + public function create($is_internal = false) { try { - $bot_id = input('bot_id',''); + $bot_id = Env::get('ai.bot_id'); $userInfo = request()->userInfo; $uid = $userInfo['id']; $companyId = $userInfo['companyId']; @@ -103,43 +112,262 @@ class ConversationController extends BaseController // 构建元数据和消息 $meta_data = [ - 'uid' => '1111', - 'companyId' => '2222', + 'uid' => strval($uid), + 'companyId' => strval($companyId), ]; $messages[] = [ 'role' => 'assistant', - 'content' => '欢迎使用美业AI助手,我可以帮您管理客户关系、自动回复消息、创建朋友圈内容,自动点赞开发客户。请问有什么可以帮你的?', + 'content' => Env::get('ai.content'), 'type' => 'answer', 'content_type' => 'text', ]; $params = [ - 'bot_id' => $bot_id, - 'meta_data' => json_encode($meta_data), - 'messages' => json_encode($messages), + 'bot_id' => strval($bot_id), + 'meta_data' => $meta_data, + 'messages' => $messages, ]; - - - $result = requestCurl($this->apiUrl . '/v1/conversation/create', $params, 'POST', $this->headers); - - + $url = $this->apiUrl . '/v1/conversation/create'; + $result = $this->httpRequest($url, 'POST', json_encode($params,256), $this->headers); $result = json_decode($result, true); - if ($result['code'] != 0) { return errorJson($result['msg'], $result['code']); } - // 获取返回的对话数据并保存 $conversation = $result['data'] ?? []; if (!empty($conversation)) { $this->saveConversation($conversation, $bot_id); + + + // 保存用户发送的消息 + $userMessageData = [ + 'chat_id' => $conversation['id'], + 'conversation_id' => $conversation['id'], + 'bot_id' => $bot_id, + 'content' => Env::get('ai.content'), + 'content_type' => 'text', + 'role' => 'assistant', + 'type' => 'answer', + 'created_at' => time(), + 'updated_at' => time() + ]; + MessageModel::create($userMessageData); + } - return successJson($conversation, '创建成功'); + if($is_internal){ + return json_encode([ + 'code' => 200, + 'data' => $conversation, + 'msg' => '创建成功' + ]); + }else{ + return successJson($conversation, '创建成功'); + } } catch (\Exception $e) { return errorJson('创建对话失败:' . $e->getMessage()); } } + + /** + * 创建对话 + */ + public function createChat() + { + try { + $bot_id = Env::get('ai.bot_id'); + $conversation_id = input('conversation_id',''); + $question = input('question',''); + + if(empty($bot_id)){ + return errorJson('智能体ID不能为空'); + } + + if(empty($conversation_id)){ + return errorJson('会话ID不能为空'); + } + + if(empty($question)){ + return errorJson('问题不能为空'); + } + + $userInfo = request()->userInfo; + $uid = $userInfo['id']; + $companyId = $userInfo['companyId']; + + // 构建请求数据 + $params = [ + 'bot_id' => strval($bot_id), + 'user_id' => strval($uid), + 'additional_messages' => [ + [ + 'role' => 'user', + 'content' => $question, + 'type' => 'question', + 'content_type' => 'text' + ] + ], + 'stream' => false, + 'auto_save_history' => true + ]; + + $url = $this->apiUrl . '/v3/chat?conversation_id='.$conversation_id; + $result = $this->httpRequest($url, 'POST', json_encode($params,256), $this->headers); + $result = json_decode($result, true); + if ($result['code'] != 0) { + return errorJson($result['msg'], $result['code']); + } + + // 保存用户发送的消息 + $userMessageData = [ + 'chat_id' => $result['data']['id'], + 'conversation_id' => $conversation_id, + 'bot_id' => $bot_id, + 'content' => $question, + 'content_type' => 'text', + 'role' => 'user', + 'type' => 'question', + 'created_at' => $result['data']['created_at'], + 'updated_at' => $result['data']['created_at'] + ]; + MessageModel::create($userMessageData); + + + return successJson($result['data'], '发送成功'); + + } catch (\Exception $e) { + return errorJson('创建对话失败:' . $e->getMessage()); + } + } + + /** + * 查看对话详情 + */ + public function chatRetrieve() + { + $conversation_id = input('conversation_id',''); + $chat_id = input('chat_id',''); + if(empty($conversation_id) && empty($chat_id)){ + return errorJson('参数缺失'); + } + $conversation = ConversationModel::where('conversation_id', $conversation_id)->find(); + if(empty($conversation)){ + return errorJson('会话不存在'); + } + + $params = [ + 'conversation_id' => $conversation_id, + 'chat_id' => $chat_id + ]; + + $url = $this->apiUrl . '/v3/chat/retrieve?' . dataBuild($params); + + $result = $this->httpRequest($url, 'GET', [], $this->headers); + $result = json_decode($result, true); + + + if ($result['code'] != 0) { + return errorJson($result['msg'], $result['code']); + } + $status = [ + 'created' => '对话已创建', + 'in_progress' => '智能体正在处理中', + 'completed' => '智能体已完成处理,本次对话结束', + 'failed' => '对话失败', + 'requires_action' => '对话中断,需要进一步处理', + 'canceled' => '对话已取消', + ]; + + $status_msg = $status[$result['data']['status']] ?? '未知状态'; + if($result['data']['status'] == 'failed'){ + $last_error = $result['data']['last_error']; + $error_msg = $status_msg . ',错误信息:' . $last_error['msg']; + return errorJson($error_msg); + }else{ + return successJson($result['data'],$status_msg); + } + } + + + + /** + * 获取对话消息详情 + */ + public function chatMessage(){ + $conversation_id = input('conversation_id',''); + $chat_id = input('chat_id',''); + if(empty($conversation_id) && empty($chat_id)){ + return errorJson('参数缺失'); + } + $conversation = ConversationModel::where('conversation_id', $conversation_id)->find(); + if(empty($conversation)){ + return errorJson('会话不存在'); + } + + $params = [ + 'conversation_id' => $conversation_id, + 'chat_id' => $chat_id + ]; + $url = $this->apiUrl . '/v3/chat/message/list?' . dataBuild($params); + $result = $this->httpRequest($url, 'GET', [], $this->headers); + $result = json_decode($result, true); + + if ($result['code'] != 0) { + return errorJson($result['msg'], $result['code']); + } + + $data = $result['data']; + $list = []; + foreach($data as $item){ + if($item['type'] == 'answer'){ + $timestamp = $item['updated_at']; + $now = time(); + $today = strtotime(date('Y-m-d')); + $yesterday = strtotime('-1 day', $today); + $thisYear = strtotime(date('Y-01-01')); + + // 格式化时间 + if($timestamp >= $today) { + $time = date('H:i', $timestamp); + } elseif($timestamp >= $yesterday) { + $time = '昨天 ' . date('H:i', $timestamp); + } elseif($timestamp >= $thisYear) { + $time = date('m-d H:i', $timestamp); + } else { + $time = date('Y-m-d H:i', $timestamp); + } + + // 保存消息记录 + $messageData = [ + 'chat_id' => $item['id'], + 'conversation_id' => $conversation_id, + 'bot_id' => $item['bot_id'], + 'content' => $item['content'], + 'content_type' => $item['content_type'], + 'role' => $item['role'], + 'type' => $item['type'], + 'created_at' => $item['created_at'], + 'updated_at' => $item['updated_at'] + ]; + + // 检查消息是否已存在 + $exists = MessageModel::where('chat_id', $item['id'])->find(); + if (!$exists) { + MessageModel::create($messageData); + } + + $list = [ + 'id' => $item['id'], + 'type' => 'assistant', + 'content' => $item['content'], + 'time' => $time + ]; + break; + } + } + + return successJson($list, '获取成功'); + } } \ No newline at end of file diff --git a/Server/application/cozeai/controller/MessageController.php b/Server/application/cozeai/controller/MessageController.php new file mode 100644 index 00000000..c62208aa --- /dev/null +++ b/Server/application/cozeai/controller/MessageController.php @@ -0,0 +1,117 @@ +userInfo; + $uid = $userInfo['id']; + $companyId = $userInfo['companyId']; + + // 获取会话ID + $conversation_id = input('conversation_id', ''); + + // 如果没有传入会话ID,则查询用户最新的会话 + if (empty($conversation_id)) { + // 查询用户是否有会话记录 + $conversation = ConversationModel::where([ + ['userId', '=', $uid], + ['companyId', '=', $companyId] + ])->order('create_time', 'desc')->find(); + + // 如果没有会话记录,创建新会话 + if (empty($conversation)) { + $conversationController = new ConversationController(); + $result = $conversationController->create(true); + $result = json_decode($result, true); + if ($result['code'] != 200) { + return errorJson('创建会话失败:' . $result['msg']); + } + + $conversation_id = $result['data']['id']; + } else { + $conversation_id = $conversation['conversation_id']; + } + } else { + // 验证会话是否属于当前用户 + $conversation = ConversationModel::where([ + ['conversation_id', '=', $conversation_id], + ['userId', '=', $uid], + ['companyId', '=', $companyId] + ])->find(); + + if (empty($conversation)) { + return errorJson('会话不存在或无权访问'); + } + } + + // 分页参数 + $page = input('page', 1); + $limit = input('limit', 20); + + // 查询消息记录 + $messages = MessageModel::where('conversation_id', $conversation_id) + ->order('id', 'DESC') + ->page($page, $limit) + ->select() + ->each(function($item) { + // 格式化时间显示 + $timestamp = $item['created_at']; + $today = strtotime(date('Y-m-d')); + $yesterday = strtotime('-1 day', $today); + $thisYear = strtotime(date('Y-01-01')); + + if($timestamp >= $today) { + $item['show_time'] = date('H:i', $timestamp); + } elseif($timestamp >= $yesterday) { + $item['show_time'] = '昨天 ' . date('H:i', $timestamp); + } elseif($timestamp >= $thisYear) { + $item['show_time'] = date('m-d H:i', $timestamp); + } else { + $item['show_time'] = date('Y-m-d H:i', $timestamp); + } + + // 根据role设置type + if ($item['role'] == 'assistant') { + $item['type'] = 'assistant'; + } else { + $item['type'] = 'user'; + } + unset($item['role']); + + return $item; + }); + + // 对消息进行倒序处理 + $messages = array_reverse($messages->toArray()); + + // 获取总记录数 + $total = MessageModel::where('conversation_id', $conversation_id)->count(); + + $data = [ + 'list' => $messages, + 'total' => $total, + 'conversation_id' => $conversation_id + ]; + + return successJson($data, '获取成功'); + + } catch (\Exception $e) { + return errorJson('获取对话记录失败:' . $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/Server/application/cozeai/model/Message.php b/Server/application/cozeai/model/Message.php new file mode 100644 index 00000000..46dcdadd --- /dev/null +++ b/Server/application/cozeai/model/Message.php @@ -0,0 +1,24 @@ + 'integer', + 'updated_at' => 'integer', + 'create_time' => 'integer', + 'update_time' => 'integer' + ]; +} \ No newline at end of file