validateAndInitParams(); if ($params === false) { return ResponseHelper::error('参数验证失败'); } // 2. 验证Tokens余额 if (!$this->checkTokensBalance($params['companyId'])) { return ResponseHelper::error('Tokens余额不足,请充值后再试'); } // 3. 获取AI配置 $setting = $this->getAiSettings($params['companyId']); if (!$setting) { return ResponseHelper::error('未找到AI配置信息,请先配置AI策略'); } // 4. 获取好友AI设置 $friendSettings = $this->getFriendSettings($params['companyId'], $params['friendId']); if (!$friendSettings) { return ResponseHelper::error('该好友未配置或未开启AI功能'); } // 5. 确保会话存在 $conversationId = $this->ensureConversation($friendSettings, $setting, $params); if (!$conversationId) { return ResponseHelper::error('创建会话失败'); } // 6. 获取历史消息 $msgData = $this->getHistoryMessages($params['friendId'], $friendSettings); // 7. 创建AI对话 $chatId = $this->createAiChat($setting, $friendSettings, $msgData); if (!$chatId) { return ResponseHelper::error('创建对话失败'); } // 8. 等待AI处理完成(轮询) $chatResult = $this->waitForChatCompletion($conversationId, $chatId); if (!$chatResult) { return ResponseHelper::error('AI处理超时或失败'); } // 9. 扣除Tokens $this->consumeTokens($chatResult, $params, $friendSettings); // 10. 获取对话消息 $messages = $this->getChatMessages($conversationId, $chatId); if (!$messages) { return ResponseHelper::error('获取对话消息失败'); } return ResponseHelper::success($messages[1]['content'], '对话成功'); } catch (\Exception $e) { \think\facade\Log::error('AI聊天异常:' . $e->getMessage()); return ResponseHelper::error('系统异常:' . $e->getMessage()); } } /** * 验证和初始化参数 * * @return array|false */ private function validateAndInitParams() { $userId = $this->getUserInfo('id'); $companyId = $this->getUserInfo('companyId'); $friendId = $this->request->param('friendId', ''); $wechatAccountId = $this->request->param('wechatAccountId', ''); if (empty($wechatAccountId) || empty($friendId)) { return false; } return [ 'userId' => $userId, 'companyId' => $companyId, 'friendId' => $friendId, 'wechatAccountId' => $wechatAccountId ]; } /** * 检查Tokens余额 * * @param int $companyId 公司ID * @return bool */ private function checkTokensBalance($companyId) { $tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens'); return !empty($tokens) && $tokens > 1000; } /** * 获取AI配置 * * @param int $companyId 公司ID * @return AiSettings|null */ private function getAiSettings($companyId) { return AiSettings::where(['companyId' => $companyId])->find(); } /** * 获取好友AI设置 * * @param int $companyId 公司ID * @param string $friendId 好友ID * @return FriendSettings|null */ private function getFriendSettings($companyId, $friendId) { $friendSettings = FriendSettings::where([ 'companyId' => $companyId, 'friendId' => $friendId ])->find(); if (empty($friendSettings) || $friendSettings->type == 0) { return null; } return $friendSettings; } /** * 确保会话存在 * * @param FriendSettings $friendSettings 好友设置 * @param AiSettings $setting AI设置 * @param array $params 参数 * @return string|null 会话ID */ private function ensureConversation($friendSettings, $setting, $params) { if (!empty($friendSettings->conversationId)) { return $friendSettings->conversationId; } // 创建新会话 $cozeAI = new CozeAI(); $data = [ 'bot_id' => $setting->botId, 'name' => '与好友' . $params['friendId'] . '的对话', 'meta_data' => [ 'friendId' => (string)$friendSettings->friendId, 'wechatAccountId' => (string)$params['wechatAccountId'], ], ]; $res = $cozeAI->createConversation($data); $res = json_decode($res, true); if ($res['code'] != 200) { \think\facade\Log::error('创建会话失败:' . ($res['msg'] ?? '未知错误')); return null; } // 保存会话ID $conversationId = $res['data']['id']; $friendSettings->conversationId = $conversationId; $friendSettings->conversationTime = time(); $friendSettings->save(); return $conversationId; } /** * 获取历史消息 * * @param string $friendId 好友ID * @param FriendSettings $friendSettings 好友设置 * @return array */ private function getHistoryMessages($friendId, $friendSettings) { $msgData = []; // 会话创建时间小于1分钟,加载最近10条消息 if ($friendSettings->conversationTime >= time() - 60) { $messages = Db::table('s2_wechat_message') ->where('wechatFriendId', $friendId) ->where('msgType', '<', 50) ->order('wechatTime desc') ->field('id,content,msgType,isSend,wechatTime') ->limit(10) ->select(); // 按时间正序排列 usort($messages, function ($a, $b) { return $a['wechatTime'] <=> $b['wechatTime']; }); // 处理聊天数据 foreach ($messages as $val) { if (empty($val['content'])) { continue; } $msg = [ 'role' => empty($val['isSend']) ? 'user' : 'assistant', 'content' => $val['content'], 'type' => empty($val['isSend']) ? 'question' : 'answer', 'content_type' => 'text' ]; $msgData[] = $msg; } } else { // 只加载最新一条用户消息 $message = Db::table('s2_wechat_message') ->where('wechatFriendId', $friendId) ->where('msgType', '<', 50) ->where('isSend', 0) ->order('wechatTime desc') ->field('id,content,msgType,isSend,wechatTime') ->find(); if (!empty($message) && !empty($message['content'])) { $msgData[] = [ 'role' => 'user', 'content' => $message['content'], 'type' => 'question', 'content_type' => 'text' ]; } } return $msgData; } /** * 创建AI对话 * * @param AiSettings $setting AI设置 * @param FriendSettings $friendSettings 好友设置 * @param array $msgData 消息数据 * @return string|null 对话ID */ private function createAiChat($setting, $friendSettings, $msgData) { $cozeAI = new CozeAI(); $data = [ 'bot_id' => $setting->botId, 'uid' => $friendSettings->friendId, 'conversation_id' => $friendSettings->conversationId, 'question' => $msgData, ]; $res = $cozeAI->createChat($data); $res = json_decode($res, true); if ($res['code'] != 200) { \think\facade\Log::error('创建对话失败:' . ($res['msg'] ?? '未知错误')); return null; } return $res['data']['id']; } /** * 等待AI处理完成(轮询机制) * * @param string $conversationId 会话ID * @param string $chatId 对话ID * @return array|null */ private function waitForChatCompletion($conversationId, $chatId) { $cozeAI = new CozeAI(); $retryCount = 0; while ($retryCount < self::MAX_RETRY_TIMES) { // 获取对话状态 $res = $cozeAI->getConversationChat([ 'conversation_id' => $conversationId, 'chat_id' => $chatId, ]); $res = json_decode($res, true); if ($res['code'] != 200) { \think\facade\Log::error('获取对话状态失败:' . ($res['msg'] ?? '未知错误')); return null; } $status = $res['data']['status'] ?? ''; // 处理不同的状态 switch ($status) { case self::STATUS_COMPLETED: // 对话完成,返回结果 return $res['data']; case self::STATUS_IN_PROGRESS: case self::STATUS_CREATED: // 继续等待 $retryCount++; sleep(self::RETRY_INTERVAL); break; case self::STATUS_FAILED: \think\facade\Log::error('对话失败,chat_id: ' . $chatId); return null; case self::STATUS_CANCELED: \think\facade\Log::error('对话已取消,chat_id: ' . $chatId); return null; case self::STATUS_REQUIRES_ACTION: \think\facade\Log::warning('对话需要进一步处理,chat_id: ' . $chatId); return null; default: \think\facade\Log::error('未知状态:' . $status); return null; } } // 超时 \think\facade\Log::error('对话处理超时,chat_id: ' . $chatId); return null; } /** * 扣除Tokens * * @param array $chatResult 对话结果 * @param array $params 参数 * @param FriendSettings $friendSettings 好友设置 */ private function consumeTokens($chatResult, $params, $friendSettings) { $tokenCount = $chatResult['usage']['token_count'] ?? 0; if (empty($tokenCount)) { return; } // 获取好友昵称 $nickname = WechatFriendModel::where('id', $friendSettings->friendId)->value('nickname'); $remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天'; // 扣除Tokens $tokensRecord = new tokensRecord(); $data = [ 'tokens' => $tokenCount * 20, 'type' => 0, 'form' => 1, 'wechatAccountId' => $params['wechatAccountId'], 'friendIdOrGroupId' => $params['friendId'], 'remarks' => $remarks, ]; $tokensRecord->consumeTokens($data); } /** * 获取对话消息 * * @param string $conversationId 会话ID * @param string $chatId 对话ID * @return array|null */ private function getChatMessages($conversationId, $chatId) { $cozeAI = new CozeAI(); $res = $cozeAI->listConversationMessage([ 'conversation_id' => $conversationId, 'chat_id' => $chatId, ]); $res = json_decode($res, true); if ($res['code'] != 200) { \think\facade\Log::error('获取对话消息失败:' . ($res['msg'] ?? '未知错误')); return null; } return $res['data'] ?? []; } public function index2222() { $userId = $this->getUserInfo('id'); $companyId = $this->getUserInfo('companyId'); $friendId = $this->request->param('friendId', ''); $wechatAccountId = $this->request->param('wechatAccountId', ''); $content = $this->request->param('content', ''); if (empty($wechatAccountId) || empty($friendId)) { return ResponseHelper::error('参数缺失'); } $tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens'); if (empty($tokens) || $tokens <= 0) { return ResponseHelper::error('用户Tokens余额不足'); } //读取AI配置 $setting = Db::name('ai_settings')->where(['companyId' => $companyId, 'userId' => $userId])->find(); if (empty($setting)) { return ResponseHelper::error('未找到配置信息,请先配置AI策略'); } $config = json_decode($setting['config'], true); $modelSetting = $config['modelSetting']; $round = isset($config['round']) ? $config['round'] : 10; // 导出聊天 $messages = Db::table('s2_wechat_message') ->where('wechatFriendId', $friendId) ->order('wechatTime desc') ->field('id,content,msgType,isSend,wechatTime') ->limit($round) ->select(); usort($messages, function ($a, $b) { return $a['wechatTime'] <=> $b['wechatTime']; }); //处理聊天数据 $msg = []; foreach ($messages as $val) { if (empty($val['content'])) { continue; } if (!empty($val['isSend'])) { $msg[] = '客服:' . $val['content']; } else { $msg[] = '用户:' . $val['content']; } } $content = implode("\n", $msg); $params = [ 'model' => 'doubao-1-5-pro-32k-250115', 'messages' => [ // ['role' => 'system', 'content' => '请完成跟客户的对话'], ['role' => 'system', 'content' => '角色设定:' . $modelSetting['role']], ['role' => 'system', 'content' => '公司背景:' . $modelSetting['businessBackground']], ['role' => 'system', 'content' => '对话风格:' . $modelSetting['dialogueStyle']], ['role' => 'user', 'content' => $content], ], ]; //AI处理 $ai = new DouBaoAI(); $res = $ai->text($params); $res = json_decode($res, true); if ($res['code'] == 200) { //扣除Tokens $tokensRecord = new tokensRecord(); $nickname = Db::table('s2_wechat_friend')->where(['id' => $friendId])->value('nickname'); $remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天'; $data = [ 'tokens' => $res['data']['token'], 'type' => 0, 'form' => 1, 'wechatAccountId' => $wechatAccountId, 'friendIdOrGroupId' => $friendId, 'remarks' => $remarks, ]; $tokensRecord->consumeTokens($data); return ResponseHelper::success($res['data']['content']); } else { return ResponseHelper::error($res['msg']); } } }