Merge branch 'develop' into yongpxu-dev
This commit is contained in:
@@ -15,8 +15,4 @@ Route::group('v1/ai', function () {
|
||||
Route::group('doubao', function () {
|
||||
Route::post('text', 'app\ai\controller\DouBaoAI@text');
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
})->middleware(['jwt']);
|
||||
370
Server/application/ai/controller/CozeAI.php
Normal file
370
Server/application/ai/controller/CozeAI.php
Normal file
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
namespace app\ai\controller;
|
||||
|
||||
use think\facade\Env;
|
||||
use think\Controller;
|
||||
|
||||
class CozeAI extends Controller
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $accessToken;
|
||||
protected $headers;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// 从环境变量获取配置
|
||||
$this->apiUrl = Env::get('cozeAi.api_url');
|
||||
$this->accessToken = Env::get('cozeAi.token');
|
||||
|
||||
if (empty($this->accessToken) || empty($this->apiUrl)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$this->headers = [
|
||||
'Authorization: Bearer ' . $this->accessToken,
|
||||
'Content-Type: application/json'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createBot($data = [])
|
||||
{
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$model_id = !empty($data['model_id']) ? $data['model_id'] : '';
|
||||
$prompt_info = !empty($data['prompt_info']) ? $data['prompt_info'] : '';
|
||||
$plugin_id_list = [
|
||||
'id_list' => [
|
||||
['api_id' => '7362852017859035163', 'plugin_id' => '7362852017859018779'],
|
||||
['api_id' => '7472045461050851367', 'plugin_id' => '7472045461050834983'],
|
||||
]
|
||||
];
|
||||
if (empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$model_info_config = [
|
||||
'model_id' => (string)$model_id,
|
||||
];
|
||||
|
||||
$params = [
|
||||
'space_id' => $space_id,
|
||||
'name' => $name,
|
||||
'model_info_config' => (object)$model_info_config,
|
||||
'plugin_id_list' => (object)$plugin_id_list
|
||||
];
|
||||
|
||||
if (!empty($prompt_info)){
|
||||
$new_prompt_info = [
|
||||
'prompt' => $prompt_info
|
||||
];
|
||||
$params['prompt_info'] = (object) $new_prompt_info;
|
||||
}
|
||||
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/create', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功', 'data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function updateBot($data = [])
|
||||
{
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$model_id = !empty($data['model_id']) ? $data['model_id'] : '';
|
||||
$prompt_info = !empty($data['prompt_info']) ? $data['prompt_info'] : '';
|
||||
$dataset_ids = !empty($data['dataset_ids']) ? $data['dataset_ids'] : '';
|
||||
$plugin_id_list = [
|
||||
'id_list' => [
|
||||
['api_id' => '7362852017859035163', 'plugin_id' => '7362852017859018779'],
|
||||
['api_id' => '7472045461050851367', 'plugin_id' => '7472045461050834983'],
|
||||
]
|
||||
];
|
||||
if (empty($name) || empty($bot_id)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$model_info_config = [
|
||||
'model_id' => (string)$model_id,
|
||||
];
|
||||
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'space_id' => $space_id,
|
||||
'name' => $name,
|
||||
'model_info_config' => (object)$model_info_config,
|
||||
'plugin_id_list' => (object)$plugin_id_list
|
||||
];
|
||||
|
||||
|
||||
if (!empty($prompt_info)){
|
||||
$new_prompt_info = [
|
||||
'prompt' => $prompt_info
|
||||
];
|
||||
$params['prompt_info'] = (object) $new_prompt_info;
|
||||
}
|
||||
|
||||
if (!empty($dataset_ids)){
|
||||
$knowledge = [
|
||||
'dataset_ids' => $dataset_ids
|
||||
];
|
||||
$params['knowledge'] = (object) $knowledge;
|
||||
}
|
||||
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/update', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '获取成功']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发布智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function botPublish($data = [])
|
||||
{
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$connector_ids = ['1024', '999'];
|
||||
if (empty($bot_id) || empty($connector_ids)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'connector_ids' => $connector_ids,
|
||||
];
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/publish', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发布成功']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createKnowledge($data = [])
|
||||
{
|
||||
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
if (empty($space_id) || empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'space_id' => $space_id,
|
||||
'format_type' => 0,
|
||||
'name' => $name,
|
||||
];
|
||||
$result = requestCurl($this->apiUrl . '/v1/datasets', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
public function createDocument($data = [])
|
||||
{
|
||||
// 文件路径
|
||||
$filePath = !empty($data['filePath']) ? $data['filePath'] : '';
|
||||
$fileName = !empty($data['fileName']) ? $data['fileName'] : '';
|
||||
if (empty($filePath)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
// 读取文件内容
|
||||
$fileContent = file_get_contents($filePath);
|
||||
// 将文件内容编码为Base64
|
||||
$base64EncodedContent = base64_encode($fileContent);
|
||||
|
||||
|
||||
$dataset_id = !empty($data['dataset_id']) ? $data['dataset_id'] : '';
|
||||
|
||||
$document_bases = [
|
||||
['name' => $fileName,'source_info' => ['file_base64' => $base64EncodedContent]]
|
||||
];
|
||||
|
||||
$chunk_strategy = [
|
||||
'chunk_type' => 0,
|
||||
'remove_extra_spaces' => true
|
||||
];
|
||||
$params = [
|
||||
'dataset_id' => (string) $dataset_id,
|
||||
'document_bases' => $document_bases,
|
||||
'chunk_strategy' => (object) $chunk_strategy,
|
||||
'format_type' => 0
|
||||
];
|
||||
$headers = array_merge($this->headers, ['Agw-Js-Conv: str']);
|
||||
$result = requestCurl($this->apiUrl . '/open_api/knowledge/document/create', $params, 'POST', $headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['document_infos']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除知识库文件
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function deleteDocument($data = [])
|
||||
{
|
||||
if (empty($data)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'document_ids' => $data,
|
||||
];
|
||||
$headers = array_merge($this->headers, ['Agw-Js-Conv: str']);
|
||||
$result = requestCurl($this->apiUrl . '/open_api/knowledge/document/delete', $params, 'POST', $headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '删除成功']);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createConversation($data = [])
|
||||
{
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$meta_data = !empty($data['meta_data']) ? $data['meta_data'] : [];
|
||||
|
||||
if (empty($bot_id) || empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'name' => $name,
|
||||
];
|
||||
if (!empty($meta_data)){
|
||||
$params['meta_data'] = $meta_data;
|
||||
}
|
||||
$result = requestCurl($this->apiUrl . '/v1/conversation/create', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开始对话
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createChat($data = [])
|
||||
{
|
||||
try {
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$uid = !empty($data['uid']) ? $data['uid'] : '';
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$question = !empty($data['question']) ? $data['question'] : [];
|
||||
|
||||
|
||||
if(empty($bot_id)){
|
||||
return errorJson('智能体ID不能为空');
|
||||
}
|
||||
|
||||
if(empty($conversation_id)){
|
||||
return errorJson('会话ID不能为空');
|
||||
}
|
||||
|
||||
if(empty($question)){
|
||||
return errorJson('问题不能为空');
|
||||
}
|
||||
|
||||
// 构建请求数据
|
||||
$params = [
|
||||
'bot_id' => strval($bot_id),
|
||||
'user_id' => strval($uid),
|
||||
'additional_messages' => $question,
|
||||
'stream' => false,
|
||||
'auto_save_history' => true
|
||||
];
|
||||
|
||||
$url = $this->apiUrl . '/v3/chat?conversation_id='.$conversation_id;
|
||||
$result = requestCurl($url, $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发送成功','data' => $result['data']]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('创建对话失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getConversationChat($data = [])
|
||||
{
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$chat_id = !empty($data['chat_id']) ? $data['chat_id'] : '';
|
||||
$url = $this->apiUrl . '/v3/chat/retrieve?conversation_id='.$conversation_id.'&chat_id='.$chat_id;
|
||||
$result = requestCurl($url, [], 'GET', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发送成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
public function listConversationMessage($data = [])
|
||||
{
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$chat_id = !empty($data['chat_id']) ? $data['chat_id'] : '';
|
||||
$url = $this->apiUrl . '/v3/chat/message/list?conversation_id='.$conversation_id.'&chat_id='.$chat_id;
|
||||
$result = requestCurl($url, [], 'GET', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return errorJson($result['msg'], $result['code']);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发送成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace app\ai\controller;
|
||||
|
||||
use think\facade\Env;
|
||||
class OpenAi
|
||||
class OpenAI
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $apiKey;
|
||||
|
||||
@@ -449,7 +449,7 @@ class MessageController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,37 +2,448 @@
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\ai\controller\DouBaoAI;
|
||||
use app\api\model\WechatFriendModel;
|
||||
use app\chukebao\controller\TokensRecordController as tokensRecord;
|
||||
use app\chukebao\model\AiSettings;
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use app\chukebao\model\TokensCompany;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* AI聊天控制器
|
||||
* 负责处理与好友的AI对话功能
|
||||
*/
|
||||
class AiChatController extends BaseController
|
||||
{
|
||||
public function index(){
|
||||
// 对话状态常量
|
||||
const STATUS_CREATED = 'created'; // 对话已创建
|
||||
const STATUS_IN_PROGRESS = 'in_progress'; // 智能体正在处理中
|
||||
const STATUS_COMPLETED = 'completed'; // 智能体已完成处理
|
||||
const STATUS_FAILED = 'failed'; // 对话失败
|
||||
const STATUS_REQUIRES_ACTION = 'requires_action'; // 对话中断,需要进一步处理
|
||||
const STATUS_CANCELED = 'canceled'; // 对话已取消
|
||||
|
||||
// 轮询配置
|
||||
const MAX_RETRY_TIMES = 30; // 最大重试次数
|
||||
const RETRY_INTERVAL = 2; // 重试间隔(秒)
|
||||
|
||||
/**
|
||||
* AI聊天主入口
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 1. 参数验证和初始化
|
||||
$params = $this->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', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
|
||||
if (empty($wechatAccountId) || empty($friendId)){
|
||||
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){
|
||||
if (empty($tokens) || $tokens <= 0) {
|
||||
return ResponseHelper::error('用户Tokens余额不足');
|
||||
}
|
||||
|
||||
|
||||
//读取AI配置
|
||||
$setting = Db::name('ai_settings')->where(['companyId' => $companyId,'userId' => $userId])->find();
|
||||
if(empty($setting)){
|
||||
$setting = Db::name('ai_settings')->where(['companyId' => $companyId, 'userId' => $userId])->find();
|
||||
if (empty($setting)) {
|
||||
return ResponseHelper::error('未找到配置信息,请先配置AI策略');
|
||||
}
|
||||
$config = json_decode($setting['config'],true);
|
||||
$config = json_decode($setting['config'], true);
|
||||
$modelSetting = $config['modelSetting'];
|
||||
$round = isset($config['round']) ? $config['round'] : 10;
|
||||
|
||||
@@ -45,20 +456,20 @@ class AiChatController extends BaseController
|
||||
->limit($round)
|
||||
->select();
|
||||
|
||||
usort($messages, function($a, $b) {
|
||||
usort($messages, function ($a, $b) {
|
||||
return $a['wechatTime'] <=> $b['wechatTime'];
|
||||
});
|
||||
|
||||
//处理聊天数据
|
||||
$msg = [];
|
||||
foreach ($messages as $val){
|
||||
if (empty($val['content'])){
|
||||
foreach ($messages as $val) {
|
||||
if (empty($val['content'])) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($val['isSend'])){
|
||||
$msg[] = '客服:' . $val['content'];
|
||||
}else{
|
||||
$msg[] = '用户:' . $val['content'];
|
||||
if (!empty($val['isSend'])) {
|
||||
$msg[] = '客服:' . $val['content'];
|
||||
} else {
|
||||
$msg[] = '用户:' . $val['content'];
|
||||
}
|
||||
}
|
||||
$content = implode("\n", $msg);
|
||||
@@ -78,13 +489,13 @@ class AiChatController extends BaseController
|
||||
//AI处理
|
||||
$ai = new DouBaoAI();
|
||||
$res = $ai->text($params);
|
||||
$res = json_decode($res,true);
|
||||
$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.'】聊天' : '与好友聊天';
|
||||
$remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天';
|
||||
$data = [
|
||||
'tokens' => $res['data']['token'],
|
||||
'type' => 0,
|
||||
@@ -95,7 +506,7 @@ class AiChatController extends BaseController
|
||||
];
|
||||
$tokensRecord->consumeTokens($data);
|
||||
return ResponseHelper::success($res['data']['content']);
|
||||
}else{
|
||||
} else {
|
||||
return ResponseHelper::error($res['msg']);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,19 +86,20 @@ class DataProcessing extends BaseController
|
||||
$msg = '好友转移成功';
|
||||
break;
|
||||
case 'CmdNewMessage':
|
||||
if(empty($friendMessage) || empty($chatroomMessage)){
|
||||
if(empty($friendMessage) && empty($chatroomMessage)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if(is_array($friendMessage) || is_array($chatroomMessage)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
if(is_array($friendMessage) && is_array($chatroomMessage)){
|
||||
return ResponseHelper::error('数据类型错误');
|
||||
}
|
||||
|
||||
|
||||
$messageController = new MessageController();
|
||||
if (!empty($friendMessage)){
|
||||
$res = $messageController->saveMessage($friendMessage);
|
||||
$res = $messageController->saveMessage($friendMessage[0]);
|
||||
}else{
|
||||
$res = $messageController->saveChatroomMessage($friendMessage);
|
||||
$res = $messageController->saveChatroomMessage($chatroomMessage[0]);
|
||||
}
|
||||
if (!empty($res)){
|
||||
$msg = '消息记录成功';
|
||||
|
||||
@@ -19,7 +19,7 @@ class MessageController extends BaseController
|
||||
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->column('id,nickname,avatar');
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId');
|
||||
|
||||
|
||||
// 构建好友子查询
|
||||
@@ -28,24 +28,42 @@ class MessageController extends BaseController
|
||||
->field('id')
|
||||
->buildSql();
|
||||
|
||||
// 使用 UNION 合并群聊和好友消息,然后统一排序和分页
|
||||
// 优化后的查询:使用MySQL兼容的查询方式
|
||||
$unionQuery = "
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId
|
||||
FROM s2_wechat_chatroom wc
|
||||
LEFT JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId
|
||||
WHERE wc.accountId = {$accountId} AND m.type = 2 AND wc.isDeleted = 0
|
||||
GROUP BY m.wechatChatroomId)
|
||||
INNER JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId AND m.type = 2
|
||||
INNER JOIN (
|
||||
SELECT wechatChatroomId, MAX(wechatTime) as maxTime, MAX(id) as maxId
|
||||
FROM s2_wechat_message
|
||||
WHERE type = 2
|
||||
GROUP BY wechatChatroomId
|
||||
) latest ON m.wechatChatroomId = latest.wechatChatroomId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
|
||||
WHERE wc.accountId = {$accountId} AND wc.isDeleted = 0
|
||||
)
|
||||
UNION ALL
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 avatar
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId
|
||||
FROM s2_wechat_message m
|
||||
INNER JOIN (
|
||||
SELECT wechatFriendId, MAX(wechatTime) as maxTime, MAX(id) as maxId
|
||||
FROM s2_wechat_message
|
||||
WHERE type = 1 AND wechatFriendId IN {$friendSubQuery}
|
||||
GROUP BY wechatFriendId
|
||||
) latest ON m.wechatFriendId = latest.wechatFriendId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
|
||||
WHERE m.type = 1 AND m.wechatFriendId IN {$friendSubQuery}
|
||||
GROUP BY m.wechatFriendId)
|
||||
ORDER BY createTime DESC
|
||||
)
|
||||
ORDER BY wechatTime DESC
|
||||
LIMIT " . (($page - 1) * $limit) . ", {$limit}
|
||||
";
|
||||
|
||||
$list = Db::query($unionQuery);
|
||||
|
||||
// 对分页后的结果进行排序(按wechatTime降序)
|
||||
usort($list, function ($a, $b) {
|
||||
return $b['wechatTime'] <=> $a['wechatTime'];
|
||||
});
|
||||
|
||||
|
||||
// 批量统计未读数量(isRead=0),按好友/群聊分别聚合
|
||||
$friendIds = [];
|
||||
$chatroomIds = [];
|
||||
@@ -62,6 +80,7 @@ class MessageController extends BaseController
|
||||
|
||||
$friendUnreadMap = [];
|
||||
if (!empty($friendIds)) {
|
||||
// 获取未读消息数量
|
||||
$friendUnreadMap = Db::table('s2_wechat_message')
|
||||
->where(['isRead' => 0])
|
||||
->whereIn('wechatFriendId', $friendIds)
|
||||
@@ -71,6 +90,7 @@ class MessageController extends BaseController
|
||||
|
||||
$chatroomUnreadMap = [];
|
||||
if (!empty($chatroomIds)) {
|
||||
// 获取未读消息数量
|
||||
$chatroomUnreadMap = Db::table('s2_wechat_message')
|
||||
->where(['isRead' => 0])
|
||||
->whereIn('wechatChatroomId', $chatroomIds)
|
||||
@@ -78,31 +98,48 @@ class MessageController extends BaseController
|
||||
->column('COUNT(*) AS cnt', 'wechatChatroomId');
|
||||
}
|
||||
|
||||
|
||||
foreach ($list as $k => &$v) {
|
||||
|
||||
$createTime = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$wechatTime = !empty($v['wechatTime']) ? date('Y-m-d H:i:s', $v['wechatTime']) : '';
|
||||
|
||||
$vunreadCount = 0;
|
||||
|
||||
$unreadCount = 0;
|
||||
if (!empty($v['wechatFriendId'])) {
|
||||
$v['nickname'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['nickname'] : '';
|
||||
$v['avatar'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['avatar'] : '';
|
||||
$vunreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
|
||||
$v['conRemark'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['conRemark'] : '';
|
||||
$v['groupId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['groupId'] : '';
|
||||
$v['wechatAccountId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatAccountId'] : '';
|
||||
$v['wechatId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatId'] : '';
|
||||
$v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : [];
|
||||
$unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
|
||||
unset($v['chatroomId']);
|
||||
}
|
||||
|
||||
if (!empty($v['wechatChatroomId'])) {
|
||||
$vunreadCount = isset($chatroomUnreadMap[$v['wechatChatroomId']]) ? (int)$chatroomUnreadMap[$v['wechatChatroomId']] : 0;
|
||||
$v['conRemark'] = '';
|
||||
$unreadCount = isset($chatroomUnreadMap[$v['wechatChatroomId']]) ? (int)$chatroomUnreadMap[$v['wechatChatroomId']] : 0;
|
||||
}
|
||||
|
||||
$v['id'] = !empty($v['wechatFriendId']) ? $v['wechatFriendId'] : $v['wechatChatroomId'];
|
||||
$v['config'] = [
|
||||
'vunreadCount' => $vunreadCount,
|
||||
'top' => false,
|
||||
'unreadCount' => $unreadCount,
|
||||
'chat' => true,
|
||||
'msgTime' => $v['wechatTime'],
|
||||
];
|
||||
$v['createTime'] = $createTime;
|
||||
$v['wechatTime'] = $wechatTime;
|
||||
unset($v['wechatFriendId'],$v['wechatChatroomId']);
|
||||
$v['lastUpdateTime'] = $wechatTime;
|
||||
|
||||
// 最新消息内容已经在UNION查询中获取,直接使用
|
||||
$v['latestMessage'] = [
|
||||
'content' => $v['content'],
|
||||
'wechatTime' => $wechatTime
|
||||
];
|
||||
|
||||
unset($v['wechatFriendId'], $v['wechatChatroomId']);
|
||||
|
||||
}
|
||||
unset($v);
|
||||
|
||||
@@ -24,10 +24,10 @@ class WechatFriendController extends BaseController
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
|
||||
// 提取所有好友ID
|
||||
/* // 提取所有好友ID
|
||||
$friendIds = array_column($list, 'id');
|
||||
|
||||
// 一次性查询所有好友的未读消息数量
|
||||
// 一次性查询所有好友的未读消息数量
|
||||
$unreadCounts = [];
|
||||
if (!empty($friendIds)) {
|
||||
$unreadResults = Db::table('s2_wechat_message')
|
||||
@@ -44,7 +44,7 @@ class WechatFriendController extends BaseController
|
||||
}
|
||||
|
||||
// 一次性查询所有好友的最新消息
|
||||
$latestMessages = [];
|
||||
$latestMessages = [];
|
||||
if (!empty($friendIds)) {
|
||||
// 使用子查询获取每个好友的最新消息ID
|
||||
$subQuery = Db::table('s2_wechat_message')
|
||||
@@ -67,9 +67,7 @@ class WechatFriendController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
$aiTypeData = [];
|
||||
@@ -87,14 +85,16 @@ class WechatFriendController extends BaseController
|
||||
$v['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s', $v['passTime']) : '';
|
||||
|
||||
|
||||
$config = [
|
||||
/* $config = [
|
||||
'unreadCount' => isset($unreadCounts[$v['id']]) ? $unreadCounts[$v['id']] : 0,
|
||||
'chat' => isset($latestMessages[$v['id']]),
|
||||
'msgTime' => isset($latestMessages[$v['id']]) ? $latestMessages[$v['id']]['wechatTime'] : 0
|
||||
];
|
||||
|
||||
// 将消息配置添加到好友数据中
|
||||
$v['config'] = $config;
|
||||
$v['config'] = $config;*/
|
||||
|
||||
|
||||
$v['aiType'] = isset($aiTypeData[$v['id']]) ? $aiTypeData[$v['id']] : 0;
|
||||
}
|
||||
unset($v);
|
||||
|
||||
52
Server/application/chukebao/model/AiKnowledgeBase.php
Normal file
52
Server/application/chukebao/model/AiKnowledgeBase.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* AI知识库模型
|
||||
*/
|
||||
class AiKnowledgeBase extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 设置JSON字段
|
||||
protected $json = ['label'];
|
||||
|
||||
// 设置JSON字段自动转换为数组
|
||||
protected $jsonAssoc = true;
|
||||
|
||||
/**
|
||||
* 关联知识库类型
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(AiKnowledgeBaseType::class, 'typeId', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的知识库列表(未删除)
|
||||
*/
|
||||
public static function getValidList($companyId, $typeId = null)
|
||||
{
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
if ($typeId !== null) {
|
||||
$where[] = ['typeId', '=', $typeId];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->with(['type'])
|
||||
->order('createTime', 'desc')
|
||||
->select();
|
||||
}
|
||||
}
|
||||
|
||||
57
Server/application/chukebao/model/AiKnowledgeBaseType.php
Normal file
57
Server/application/chukebao/model/AiKnowledgeBaseType.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* AI知识库类型模型
|
||||
*/
|
||||
class AiKnowledgeBaseType extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base_type';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 设置JSON字段
|
||||
protected $json = ['label'];
|
||||
|
||||
// 设置JSON字段自动转换为数组
|
||||
protected $jsonAssoc = true;
|
||||
|
||||
// 类型常量
|
||||
const TYPE_SYSTEM = 0; // 系统类型
|
||||
const TYPE_USER = 1; // 用户创建类型
|
||||
|
||||
/**
|
||||
* 获取有效的类型列表(未删除)
|
||||
*/
|
||||
public static function getValidList($companyId, $includeSystem = true)
|
||||
{
|
||||
$where = [
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
|
||||
if ($includeSystem) {
|
||||
$where[] = ['type|companyId', 'in', [0, $companyId]];
|
||||
} else {
|
||||
$where[] = ['companyId', '=', $companyId];
|
||||
$where[] = ['type', '=', self::TYPE_USER];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->order('createTime', 'desc')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为系统类型
|
||||
*/
|
||||
public function isSystemType()
|
||||
{
|
||||
return $this->type == self::TYPE_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
17
Server/application/chukebao/model/AiSettings.php
Normal file
17
Server/application/chukebao/model/AiSettings.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class AiSettings extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'ai_settings';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
@@ -160,6 +160,26 @@ Route::group('v1/', function () {
|
||||
|
||||
|
||||
|
||||
//AI知识库
|
||||
Route::group('knowledge', function () {
|
||||
Route::get('init', 'app\cunkebao\controller\AiSettingsController@init');
|
||||
Route::get('release', 'app\cunkebao\controller\AiSettingsController@release');
|
||||
Route::get('typeList', 'app\cunkebao\controller\AiKnowledgeBaseController@typeList');
|
||||
Route::get('getList', 'app\cunkebao\controller\AiKnowledgeBaseController@getList');
|
||||
Route::post('add', 'app\cunkebao\controller\AiKnowledgeBaseController@add');
|
||||
//Route::post('edit', 'app\cunkebao\controller\AiKnowledgeBaseController@edit');
|
||||
Route::delete('delete', 'app\cunkebao\controller\AiKnowledgeBaseController@delete');
|
||||
//Route::get('detail', 'app\cunkebao\controller\AiKnowledgeBaseController@detail');
|
||||
Route::post('update', 'app\cunkebao\controller\AiKnowledgeBaseController@update');
|
||||
Route::post('delete', 'app\cunkebao\controller\AiKnowledgeBaseController@delete');
|
||||
Route::post('addType', 'app\cunkebao\controller\AiKnowledgeBaseController@addType');
|
||||
Route::post('editType', 'app\cunkebao\controller\AiKnowledgeBaseController@editType');
|
||||
Route::delete('deleteType', 'app\cunkebao\controller\AiKnowledgeBaseController@deleteType');
|
||||
Route::get('detailType', 'app\cunkebao\controller\AiKnowledgeBaseController@detailType');
|
||||
});
|
||||
|
||||
|
||||
|
||||
})->middleware(['jwt']);
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,570 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\chukebao\model\AiKnowledgeBaseType;
|
||||
use app\chukebao\model\AiKnowledgeBase;
|
||||
use app\chukebao\model\AiSettings as AiSettingsModel;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* AI知识库管理控制器
|
||||
* 负责管理AI知识库类型和知识库内容
|
||||
*/
|
||||
class AiKnowledgeBaseController extends BaseController
|
||||
{
|
||||
// ==================== 知识库类型管理 ====================
|
||||
|
||||
/**
|
||||
* 获取知识库类型列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function typeList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page', 1);
|
||||
$pageSize = $this->request->param('pageSize', 20);
|
||||
$includeSystem = $this->request->param('includeSystem', 1); // 是否包含系统类型
|
||||
|
||||
// 构建查询条件
|
||||
$where = [['isDel', '=', 0]];
|
||||
|
||||
if ($includeSystem == 1) {
|
||||
// 包含系统类型和本公司创建的类型
|
||||
$where[] = ['type', '=', AiKnowledgeBaseType::TYPE_SYSTEM];
|
||||
$where[] = ['companyId|type', 'in', [$companyId, 0]];
|
||||
} else {
|
||||
// 只显示本公司创建的类型
|
||||
$where[] = ['companyId', '=', $companyId];
|
||||
$where[] = ['type', '=', AiKnowledgeBaseType::TYPE_USER];
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$list = AiKnowledgeBaseType::where($where)
|
||||
->order('type', 'asc') // 系统类型排在前面
|
||||
->order('createTime', 'desc')
|
||||
->paginate($pageSize, false, ['page' => $page]);
|
||||
|
||||
return ResponseHelper::success($list, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addType()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$name = $this->request->param('name', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$prompt = $this->request->param('prompt', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('类型名称不能为空');
|
||||
}
|
||||
|
||||
// 检查名称是否重复
|
||||
$exists = AiKnowledgeBaseType::where([
|
||||
['companyId', '=', $companyId],
|
||||
['name', '=', $name],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return ResponseHelper::error('该类型名称已存在');
|
||||
}
|
||||
|
||||
// 创建类型
|
||||
$typeModel = new AiKnowledgeBaseType();
|
||||
$data = [
|
||||
'type' => AiKnowledgeBaseType::TYPE_USER,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'label' => json_decode($label,256),
|
||||
'prompt' => $prompt,
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'isDel' => 0
|
||||
];
|
||||
|
||||
if ($typeModel->save($data)) {
|
||||
return ResponseHelper::success(['id' => $typeModel->id], '添加成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function editType()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$prompt = $this->request->param('prompt', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('类型名称不能为空');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查是否为系统类型
|
||||
if ($typeModel->isSystemType()) {
|
||||
return ResponseHelper::error('系统类型不允许编辑');
|
||||
}
|
||||
|
||||
// 检查权限(只能编辑本公司的类型)
|
||||
if ($typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限编辑该类型');
|
||||
}
|
||||
|
||||
// 检查名称是否重复(排除自己)
|
||||
$exists = AiKnowledgeBaseType::where([
|
||||
['companyId', '=', $companyId],
|
||||
['name', '=', $name],
|
||||
['id', '<>', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return ResponseHelper::error('该类型名称已存在');
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$typeModel->name = $name;
|
||||
$typeModel->description = $description;
|
||||
$typeModel->label = $label;
|
||||
$typeModel->prompt = $prompt;
|
||||
$typeModel->updateTime = time();
|
||||
|
||||
if ($typeModel->save()) {
|
||||
return ResponseHelper::success([], '更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteType()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查是否为系统类型
|
||||
if ($typeModel->isSystemType()) {
|
||||
return ResponseHelper::error('系统类型不允许删除');
|
||||
}
|
||||
|
||||
// 检查权限(只能删除本公司的类型)
|
||||
if ($typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限删除该类型');
|
||||
}
|
||||
|
||||
// 检查是否有关联的知识库
|
||||
$hasKnowledge = AiKnowledgeBase::where([
|
||||
['typeId', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->count();
|
||||
|
||||
if ($hasKnowledge > 0) {
|
||||
return ResponseHelper::error('该类型下存在知识库,无法删除');
|
||||
}
|
||||
|
||||
// 软删除
|
||||
$typeModel->isDel = 1;
|
||||
$typeModel->delTime = time();
|
||||
|
||||
if ($typeModel->save()) {
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} else {
|
||||
return ResponseHelper::error('删除失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 知识库管理 ====================
|
||||
|
||||
/**
|
||||
* 获取知识库列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page', 1);
|
||||
$pageSize = $this->request->param('pageSize', 20);
|
||||
$typeId = $this->request->param('typeId', 0); // 类型筛选
|
||||
$keyword = $this->request->param('keyword', ''); // 关键词搜索
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
if ($typeId > 0) {
|
||||
$where[] = ['typeId', '=', $typeId];
|
||||
}
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$list = AiKnowledgeBase::where($where)
|
||||
->with(['type'])
|
||||
->order('createTime', 'desc')
|
||||
->paginate($pageSize, false, ['page' => $page]);
|
||||
|
||||
return ResponseHelper::success($list, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
$datasetId = AiSettingsModel::where(['companyId' => $companyId])->value('datasetId');
|
||||
|
||||
|
||||
// 获取参数
|
||||
$typeId = $this->request->param('typeId', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($typeId)) {
|
||||
return ResponseHelper::error('请选择知识库类型');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('知识库名称不能为空');
|
||||
}
|
||||
|
||||
if (empty($fileUrl)) {
|
||||
return ResponseHelper::error('文件地址不能为空');
|
||||
}
|
||||
|
||||
// 检查类型是否存在
|
||||
$typeExists = AiKnowledgeBaseType::where([
|
||||
['id', '=', $typeId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeExists) {
|
||||
return ResponseHelper::error('知识库类型不存在');
|
||||
}
|
||||
|
||||
// 创建知识库
|
||||
$knowledgeModel = new AiKnowledgeBase();
|
||||
$data = [
|
||||
'typeId' => $typeId,
|
||||
'name' => $name,
|
||||
'label' => json_encode($label, 256),
|
||||
'fileUrl' => $fileUrl,
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'isDel' => 0
|
||||
];
|
||||
|
||||
if ($knowledgeModel->save($data)) {
|
||||
if (!empty($datasetId)) {
|
||||
$createDocumentData = [
|
||||
'filePath' => $fileUrl,
|
||||
'fileName' => $name,
|
||||
'dataset_id' => $datasetId
|
||||
];
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createDocument($createDocumentData);
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] == 200) {
|
||||
$documentId = $result['data'][0]['document_id'];
|
||||
AiKnowledgeBase::where('id', $knowledgeModel->id)->update(['documentId' => $documentId, 'updateTime' => time()]);
|
||||
AiSettingsModel::where(['companyId' => $companyId])->update(['isRelease' => 0,'updateTime' => time()]);
|
||||
}
|
||||
}
|
||||
return ResponseHelper::success(['id' => $knowledgeModel->id], '添加成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$typeId = $this->request->param('typeId', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($typeId)) {
|
||||
return ResponseHelper::error('请选择知识库类型');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('知识库名称不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledgeModel = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$knowledgeModel) {
|
||||
return ResponseHelper::error('知识库不存在或无权限编辑');
|
||||
}
|
||||
|
||||
// 检查类型是否存在
|
||||
$typeExists = AiKnowledgeBaseType::where([
|
||||
['id', '=', $typeId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeExists) {
|
||||
return ResponseHelper::error('知识库类型不存在');
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$knowledgeModel->typeId = $typeId;
|
||||
$knowledgeModel->name = $name;
|
||||
$knowledgeModel->label = json_encode($label, 256);
|
||||
if (!empty($fileUrl)) {
|
||||
$knowledgeModel->fileUrl = $fileUrl;
|
||||
}
|
||||
$knowledgeModel->updateTime = time();
|
||||
|
||||
if ($knowledgeModel->save()) {
|
||||
return ResponseHelper::success([], '更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledgeModel = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$knowledgeModel) {
|
||||
return ResponseHelper::error('知识库不存在或无权限删除');
|
||||
}
|
||||
|
||||
// 软删除
|
||||
$knowledgeModel->isDel = 1;
|
||||
$knowledgeModel->delTime = time();
|
||||
|
||||
if ($knowledgeModel->save()) {
|
||||
if (!empty($knowledgeModel->documentId)){
|
||||
$cozeAI = new CozeAI();
|
||||
$cozeAI->deleteDocument([$knowledgeModel->documentId]);
|
||||
AiSettingsModel::where(['companyId' => $companyId])->update(['isRelease' => 0,'updateTime' => time()]);
|
||||
}
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} else {
|
||||
return ResponseHelper::error('删除失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledge = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->with(['type'])->find();
|
||||
|
||||
if (!$knowledge) {
|
||||
return ResponseHelper::error('知识库不存在或无权限查看');
|
||||
}
|
||||
|
||||
return ResponseHelper::success($knowledge, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
344
Server/application/cunkebao/controller/AiSettingsController.php
Normal file
344
Server/application/cunkebao/controller/AiSettingsController.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\api\model\CompanyModel;
|
||||
use app\chukebao\model\AiSettings as AiSettingsModel;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* AI设置控制器
|
||||
* 负责管理公司的AI智能体配置,包括创建智能体、知识库等
|
||||
*/
|
||||
class AiSettingsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 初始化AI设置
|
||||
* 检查公司是否已有AI配置,如果没有则创建默认配置
|
||||
* 自动创建智能体和知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
try {
|
||||
// 获取当前用户信息
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 查找公司AI设置
|
||||
$settings = $this->getOrCreateAiSettings($companyId, $userId);
|
||||
|
||||
if (!$settings) {
|
||||
return ResponseHelper::error('AI设置初始化失败');
|
||||
}
|
||||
|
||||
// 确保智能体已创建
|
||||
if (empty($settings->botId)) {
|
||||
$botCreated = $this->createBot($settings);
|
||||
if (!$botCreated) {
|
||||
return ResponseHelper::error('智能体创建失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 确保知识库已创建
|
||||
if (empty($settings->datasetId)) {
|
||||
$knowledgeCreated = $this->createKnowledge($settings);
|
||||
if (!$knowledgeCreated) {
|
||||
return ResponseHelper::error('知识库创建失败');
|
||||
}
|
||||
}
|
||||
if (!empty($settings->botId) && !empty($settings->datasetId)) {
|
||||
$cozeAI = new CozeAI();
|
||||
$config = json_decode($settings->config,true);
|
||||
$config['bot_id'] = $settings->botId;
|
||||
$config['dataset_ids'] = [$settings->datasetId];
|
||||
$cozeAI->updateBot($config);
|
||||
}
|
||||
|
||||
// 解析配置信息
|
||||
$settings->config = json_decode($settings->config, true);
|
||||
|
||||
return ResponseHelper::success($settings, 'AI设置初始化成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建AI设置
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $userId 用户ID
|
||||
* @return AiSettingsModel|false
|
||||
*/
|
||||
private function getOrCreateAiSettings($companyId, $userId)
|
||||
{
|
||||
// 查找现有设置
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
|
||||
if (empty($settings)) {
|
||||
// 获取公司信息
|
||||
$company = CompanyModel::where('id', $companyId)->find();
|
||||
if (empty($company)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建默认配置
|
||||
$config = $this->getDefaultConfig($company['name']);
|
||||
|
||||
// 保存AI设置
|
||||
$settings = $this->saveAiSettings($companyId, $userId, $config);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认AI配置
|
||||
*
|
||||
* @param string $companyName 公司名称
|
||||
* @return array
|
||||
*/
|
||||
private function getDefaultConfig($companyName)
|
||||
{
|
||||
return [
|
||||
'name' => $companyName,
|
||||
'model_id' => '1737521813', // 默认模型ID
|
||||
'prompt_info' => $this->getDefaultPrompt()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认提示词
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getDefaultPrompt()
|
||||
{
|
||||
return '# 角色
|
||||
你是一位全能知识客服,作为专业的客服智能体,具备全面的知识储备,能够回答用户提出的各类问题。在回答问题前,会仔细查阅知识库内容,并且始终严格遵守中国法律法规。
|
||||
|
||||
## 技能
|
||||
### 技能 1: 回答用户问题
|
||||
1. 当用户提出问题时,首先在知识库中进行搜索查找相关信息。
|
||||
2. 依据知识库中的内容,为用户提供准确、清晰、完整的回答。
|
||||
|
||||
## 限制
|
||||
- 仅依据知识库内容回答问题,对于知识库中没有的信息,如实告知用户无法回答。
|
||||
- 回答必须严格遵循中国法律法规,不得出现任何违法违规内容。
|
||||
- 回答需简洁明了,避免冗长复杂的表述。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存AI设置到数据库
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $userId 用户ID
|
||||
* @param array $config 配置信息
|
||||
* @return AiSettingsModel|false
|
||||
*/
|
||||
private function saveAiSettings($companyId, $userId, $config)
|
||||
{
|
||||
$data = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'config' => json_encode($config, JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'botId' => 0,
|
||||
'datasetId' => 0,
|
||||
];
|
||||
|
||||
$aiSettingsModel = new AiSettingsModel();
|
||||
$result = $aiSettingsModel->save($data);
|
||||
|
||||
if ($result) {
|
||||
return AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AI智能体
|
||||
*
|
||||
* @param AiSettingsModel $settings AI设置对象
|
||||
* @return bool
|
||||
*/
|
||||
private function createBot($settings)
|
||||
{
|
||||
if (empty($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = json_decode($settings->config, true);
|
||||
if (empty($config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用CozeAI创建智能体
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createBot($config);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 200) {
|
||||
\think\facade\Log::error('智能体创建失败:' . ($result['msg'] ?? '未知错误'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新智能体ID
|
||||
$settings->botId = $result['data']['bot_id'];
|
||||
$settings->updateTime = time();
|
||||
|
||||
return $settings->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('创建智能体异常:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
*
|
||||
* @param AiSettingsModel $settings AI设置对象
|
||||
* @return bool
|
||||
*/
|
||||
private function createKnowledge($settings)
|
||||
{
|
||||
if (empty($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = json_decode($settings->config, true);
|
||||
if (empty($config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用CozeAI创建知识库
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createKnowledge(['name' => $config['name']]);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 200) {
|
||||
\think\facade\Log::error('知识库创建失败:' . ($result['msg'] ?? '未知错误'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新知识库ID
|
||||
$settings->datasetId = $result['data']['dataset_id'];
|
||||
$settings->updateTime = time();
|
||||
|
||||
return $settings->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('创建知识库异常:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新AI配置
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateConfig()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取请求参数
|
||||
$config = $this->request->param('config', []);
|
||||
if (empty($config)) {
|
||||
return ResponseHelper::error('配置参数不能为空');
|
||||
}
|
||||
|
||||
// 查找现有设置
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (empty($settings)) {
|
||||
return ResponseHelper::error('AI设置不存在,请先初始化');
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
$settings->config = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||
$settings->updateTime = time();
|
||||
|
||||
if ($settings->save()) {
|
||||
return ResponseHelper::success([], '配置更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('配置更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI设置详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (empty($settings)) {
|
||||
return ResponseHelper::error('AI设置不存在');
|
||||
}
|
||||
|
||||
// 解析配置信息
|
||||
$settings->config = json_decode($settings->config, true);
|
||||
|
||||
return ResponseHelper::success($settings, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发布智能体
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (!empty($settings->isRelease)) {
|
||||
return ResponseHelper::success('', '已发布,无需重复发布');
|
||||
}
|
||||
|
||||
$cozeAI = new CozeAI();
|
||||
$res = $cozeAI->botPublish(['bot_id' => $settings->botId]);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
$msg = '发布失败失败:' . ($res['msg'] ?? '未知错误');
|
||||
return ResponseHelper::error($msg);
|
||||
}
|
||||
$settings->isRelease = 1;
|
||||
$settings->releaseTime = time();
|
||||
$settings->save();
|
||||
return ResponseHelper::success('', '发布成功');
|
||||
}
|
||||
}
|
||||
11
Server/application/cunkebao/model/AiKnowledgeBase.php
Normal file
11
Server/application/cunkebao/model/AiKnowledgeBase.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace app\cunkebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
|
||||
class AiKnowledgeBase extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base';
|
||||
}
|
||||
11
Server/application/cunkebao/model/AiKnowledgeBaseType.php
Normal file
11
Server/application/cunkebao/model/AiKnowledgeBaseType.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace app\cunkebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
|
||||
class AiKnowledgeBaseType extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base_type';
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace AccountWeight\UnitWeight;
|
||||
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use library\interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\Interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
|
||||
class ActivityWeigth implements WechatAccountWeightResultSetInterface
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace AccountWeight\UnitWeight;
|
||||
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use library\interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\Interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
|
||||
class AgeWeight implements WechatAccountWeightResultSetInterface
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace AccountWeight\UnitWeight;
|
||||
|
||||
use library\interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\Interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
|
||||
class RealNameWeight implements WechatAccountWeightResultSetInterface
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace AccountWeight\UnitWeight;
|
||||
|
||||
use app\common\model\WechatRestricts as WechatRestrictsModel;
|
||||
use library\interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\Interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
|
||||
class RestrictWeight implements WechatAccountWeightResultSetInterface
|
||||
{
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace AccountWeight;
|
||||
|
||||
use AccountWeight\Exceptions\WechatAccountWeightAssessmentException as WeightAssessmentException;
|
||||
use library\ClassTable;
|
||||
use library\interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
use library\Interfaces\WechatAccountWeightResultSet as WechatAccountWeightResultSetInterface;
|
||||
use library\Interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
use AccountWeight\UnitWeight;
|
||||
use app\common\service\ClassTableService;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace AccountWeight;
|
||||
|
||||
use library\interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
use library\interfaces\WechatFriendAddLimitAssessment as WechatFriendAddLimitAssessmentInterface;
|
||||
use library\Interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
use library\Interfaces\WechatFriendAddLimitAssessment as WechatFriendAddLimitAssessmentInterface;
|
||||
|
||||
class WechatFriendAddLimitAssessment implements WechatFriendAddLimitAssessmentInterface
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace library;
|
||||
|
||||
use library\interfaces\CallMap as CallMapInterface;
|
||||
use library\Interfaces\CallMap as CallMapInterface;
|
||||
|
||||
class ClassTable implements CallMapInterface
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace library\interfaces;
|
||||
namespace library\Interfaces;
|
||||
|
||||
interface CallMap
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace library\interfaces;
|
||||
namespace library\Interfaces;
|
||||
|
||||
use AccountWeight\Exceptions\WechatAccountWeightAssessmentException as WeightAssessmentException;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace library\interfaces;
|
||||
namespace library\Interfaces;
|
||||
|
||||
/**
|
||||
* 微信账号加友权重评估结果数据
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace library\interfaces;
|
||||
namespace library\Interfaces;
|
||||
|
||||
use library\interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
use library\Interfaces\WechatAccountWeightAssessment as WechatAccountWeightAssessmentInterface;
|
||||
|
||||
/**
|
||||
* 微信账号加友权重评估
|
||||
|
||||
Reference in New Issue
Block a user