diff --git a/app/Http/Controllers/Api/AssistantController.php b/app/Http/Controllers/Api/AssistantController.php
new file mode 100644
index 000000000..268b8da5c
--- /dev/null
+++ b/app/Http/Controllers/Api/AssistantController.php
@@ -0,0 +1,251 @@
+checkChatInformation();
+ //
+ $dialog_id = intval(Request::input('dialog_id'));
+ $content = trim(Request::input('content', ''));
+ if ($dialog_id <= 0) {
+ return Base::retError('参数错误');
+ }
+
+ $dialog = WebSocketDialog::checkDialog($dialog_id);
+
+ // 基本信息
+ $context = [
+ 'dialog_name' => $dialog->name ?: '',
+ 'dialog_type' => $dialog->type ?: '',
+ 'group_type' => $dialog->group_type ?: '',
+ ];
+
+ // 当前草稿
+ $draft = Request::input('draft', '');
+ if (is_string($draft) && trim($draft) !== '') {
+ $context['current_draft'] = Base::html2markdown($draft);
+ }
+
+ // 引用消息
+ $quote_id = intval(Request::input('quote_id'));
+ if ($quote_id > 0) {
+ $quote = WebSocketDialogMsg::whereDialogId($dialog_id)
+ ->whereId($quote_id)
+ ->with('user')
+ ->first();
+ if ($quote) {
+ $context['quote_summary'] = WebSocketDialogMsg::previewMsg($quote);
+ $context['quote_user'] = $quote->user->nickname ?? '';
+ }
+ }
+
+ // 成员列表
+ $members = WebSocketDialogUser::whereDialogId($dialog_id)
+ ->join('users', 'users.userid', '=', 'web_socket_dialog_users.userid')
+ ->orderBy('web_socket_dialog_users.id')
+ ->limit(10)
+ ->pluck('users.nickname')
+ ->filter()
+ ->values()
+ ->all();
+ if (!empty($members)) {
+ $context['members'] = $members;
+ }
+
+ // 最近消息
+ $recentMessagesQuery = WebSocketDialogMsg::whereDialogId($dialog_id)
+ ->orderByDesc('id')
+ ->with('user');
+
+ $recentMessages = (clone $recentMessagesQuery)->take(15)->get();
+ if ($recentMessages->isNotEmpty()) {
+ $formatRecentMessages = function ($messages) {
+ return $messages->reverse()->map(function ($msg) {
+ return [
+ 'sender' => $msg->user->nickname ?? ('用户' . $msg->userid),
+ 'summary' => $msg->extractMessageContent(500),
+ ];
+ })->filter(function ($item) {
+ return !empty($item['summary']);
+ })->values();
+ };
+
+ $formattedRecentMessages = $formatRecentMessages($recentMessages);
+ $summaryLength = $formattedRecentMessages->sum(function ($item) {
+ return mb_strlen($item['summary']);
+ });
+
+ if ($summaryLength < 500 && $recentMessages->count() === 15) {
+ $lastMessageId = optional($recentMessages->last())->id;
+ $additionalMessages = collect();
+ if ($lastMessageId) {
+ $additionalMessages = (clone $recentMessagesQuery)
+ ->where('id', '<', $lastMessageId)
+ ->take(10)
+ ->get();
+ }
+ if ($additionalMessages->isNotEmpty()) {
+ $recentMessages = $recentMessages->concat($additionalMessages);
+ $formattedRecentMessages = $formatRecentMessages($recentMessages);
+ }
+ }
+
+ if ($formattedRecentMessages->isNotEmpty()) {
+ $context['recent_messages'] = $formattedRecentMessages->all();
+ }
+ }
+
+ // 生成消息
+ $systemPrompt = AI::messageSystemPrompt();
+ $contextPrompt = AI::buildMessageContextPrompt($context);
+ if ($content) {
+ $contextPrompt .= "\n\n请根据以上信息,结合提示词生成一条待发送的消息:";
+ }
+
+ return Base::retSuccess('success', [
+ 'system_prompt' => $systemPrompt,
+ 'context_prompt' => $contextPrompt,
+ ]);
+ }
+
+ /**
+ * @api {post} api/assistant/auth 生成授权码
+ *
+ * @apiDescription 需要token身份,生成 AI 流式会话的 stream_key
+ * @apiVersion 1.0.0
+ * @apiGroup assistant
+ * @apiName auth
+ *
+ * @apiParam {String} model_type 模型类型
+ * @apiParam {String} model_name 模型名称
+ * @apiParam {JSON} context 上下文数组
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ * @apiSuccess {String} data.stream_key 流式会话凭证
+ */
+ public function auth()
+ {
+ $user = User::auth();
+ $user->checkChatInformation();
+
+ $modelType = trim(Request::input('model_type', ''));
+ $modelName = trim(Request::input('model_name', ''));
+ $contextInput = Request::input('context', []);
+
+ if ($modelType === '' || $modelName === '') {
+ return Base::retError('参数错误');
+ }
+
+ if (is_string($contextInput)) {
+ $decoded = json_decode($contextInput, true);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ $contextInput = $decoded;
+ }
+ }
+ if (!is_array($contextInput)) {
+ return Base::retError('context 参数格式错误');
+ }
+
+ $context = [];
+ foreach ($contextInput as $item) {
+ if (!is_array($item) || count($item) < 2) {
+ continue;
+ }
+ $role = trim((string)($item[0] ?? ''));
+ $message = trim((string)($item[1] ?? ''));
+ if ($role === '' || $message === '') {
+ continue;
+ }
+ $context[] = [$role, $message];
+ }
+
+ $contextJson = json_encode($context, JSON_UNESCAPED_UNICODE);
+ if ($contextJson === false) {
+ return Base::retError('context 参数格式错误');
+ }
+
+ $setting = Base::setting('aibotSetting');
+ $apiKey = Base::val($setting, $modelType . '_key');
+ if ($modelType === 'wenxin') {
+ $wenxinSecret = Base::val($setting, 'wenxin_secret');
+ if ($wenxinSecret) {
+ $apiKey = trim(($apiKey ?: '') . ':' . $wenxinSecret);
+ }
+ }
+ if ($modelType === 'ollama' && empty($apiKey)) {
+ $apiKey = Base::strRandom(6);
+ }
+ if (empty($apiKey)) {
+ return Base::retError('模型未启用');
+ }
+
+ $remoteModelType = match ($modelType) {
+ 'qianwen' => 'qwen',
+ default => $modelType,
+ };
+
+ $authResult = Ihttp::ihttp_post('http://nginx/ai/invoke/auth', [
+ 'api_key' => $apiKey,
+ 'model_type' => $remoteModelType,
+ 'model_name' => $modelName,
+ 'context' => $contextJson,
+ ], 30);
+
+ if (Base::isError($authResult)) {
+ return Base::retError($authResult['msg']);
+ }
+
+ $body = Base::json2array($authResult['data']);
+ if ($body['code'] !== 200) {
+ return Base::retError($body['error'] ?: 'AI 接口返回异常', $body);
+ }
+
+ $streamKey = Base::val($body, 'data.stream_key');
+ if (empty($streamKey)) {
+ return Base::retError('AI 接口返回数据异常');
+ }
+
+ return Base::retSuccess('success', [
+ 'stream_key' => $streamKey,
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php
index 65ab05e68..4f7373a9e 100755
--- a/app/Http/Controllers/Api/DialogController.php
+++ b/app/Http/Controllers/Api/DialogController.php
@@ -1026,133 +1026,6 @@ class DialogController extends AbstractController
return Base::retSuccess('success');
}
- /**
- * @api {post} api/dialog/msg/aiprompt AI 提示词助手
- *
- * @apiDescription 需要token身份,整理当前会话的系统提示词与上下文信息
- * @apiVersion 1.0.0
- * @apiGroup dialog
- * @apiName msg__aiprompt
- *
- * @apiParam {Number} dialog_id 对话ID
- * @apiParam {String} content 消息需求描述(用于提示词整理,可为空)
- * @apiParam {String} [draft] 当前草稿内容(HTML 格式)
- * @apiParam {Number} [quote_id] 引用消息ID
- *
- * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
- * @apiSuccess {String} msg 返回信息(错误描述)
- * @apiSuccess {Object} data 返回数据
- * @apiSuccess {String} data.system_prompt AI 使用的系统提示词
- * @apiSuccess {String} data.context_prompt AI 使用的上下文提示词
- */
- public function msg__aiprompt()
- {
- $user = User::auth();
- $user->checkChatInformation();
- //
- $dialog_id = intval(Request::input('dialog_id'));
- $content = trim(Request::input('content', ''));
- if ($dialog_id <= 0) {
- return Base::retError('参数错误');
- }
-
- $dialog = WebSocketDialog::checkDialog($dialog_id);
-
- // 基本信息
- $context = [
- 'dialog_name' => $dialog->name ?: '',
- 'dialog_type' => $dialog->type ?: '',
- 'group_type' => $dialog->group_type ?: '',
- ];
-
- // 当前草稿
- $draft = Request::input('draft', '');
- if (is_string($draft) && trim($draft) !== '') {
- $context['current_draft'] = Base::html2markdown($draft);
- }
-
- // 引用消息
- $quote_id = intval(Request::input('quote_id'));
- if ($quote_id > 0) {
- $quote = WebSocketDialogMsg::whereDialogId($dialog_id)
- ->whereId($quote_id)
- ->with('user')
- ->first();
- if ($quote) {
- $context['quote_summary'] = WebSocketDialogMsg::previewMsg($quote);
- $context['quote_user'] = $quote->user->nickname ?? '';
- }
- }
-
- // 成员列表
- $members = WebSocketDialogUser::whereDialogId($dialog_id)
- ->join('users', 'users.userid', '=', 'web_socket_dialog_users.userid')
- ->orderBy('web_socket_dialog_users.id')
- ->limit(10)
- ->pluck('users.nickname')
- ->filter()
- ->values()
- ->all();
- if (!empty($members)) {
- $context['members'] = $members;
- }
-
- // 最近消息
- $recentMessagesQuery = WebSocketDialogMsg::whereDialogId($dialog_id)
- ->orderByDesc('id')
- ->with('user');
-
- $recentMessages = (clone $recentMessagesQuery)->take(15)->get();
- if ($recentMessages->isNotEmpty()) {
- $formatRecentMessages = function ($messages) {
- return $messages->reverse()->map(function ($msg) {
- return [
- 'sender' => $msg->user->nickname ?? ('用户' . $msg->userid),
- 'summary' => $msg->extractMessageContent(500),
- ];
- })->filter(function ($item) {
- return !empty($item['summary']);
- })->values();
- };
-
- $formattedRecentMessages = $formatRecentMessages($recentMessages);
- $summaryLength = $formattedRecentMessages->sum(function ($item) {
- return mb_strlen($item['summary']);
- });
-
- if ($summaryLength < 500 && $recentMessages->count() === 15) {
- $lastMessageId = optional($recentMessages->last())->id;
- $additionalMessages = collect();
- if ($lastMessageId) {
- $additionalMessages = (clone $recentMessagesQuery)
- ->where('id', '<', $lastMessageId)
- ->take(10)
- ->get();
- }
- if ($additionalMessages->isNotEmpty()) {
- $recentMessages = $recentMessages->concat($additionalMessages);
- $formattedRecentMessages = $formatRecentMessages($recentMessages);
- }
- }
-
- if ($formattedRecentMessages->isNotEmpty()) {
- $context['recent_messages'] = $formattedRecentMessages->all();
- }
- }
-
- // 生成消息
- $systemPrompt = AI::messageSystemPrompt();
- $contextPrompt = AI::buildMessageContextPrompt($context);
- if ($content) {
- $contextPrompt .= "\n\n请根据以上信息,结合提示词生成一条待发送的消息:\n\n";
- }
-
- return Base::retSuccess('success', [
- 'system_prompt' => $systemPrompt,
- 'context_prompt' => $contextPrompt,
- ]);
- }
-
/**
* @api {post} api/dialog/msg/sendtext 发送消息
*
@@ -1180,7 +1053,6 @@ class DialogController extends AbstractController
* - no: 正常发送(默认)
* - yes: 静默发送
* @apiParam {String} [model_name] 模型名称(仅AI机器人支持)
- * @apiParam {Object} [extra_data] 附加数据(保存到附加表)
*
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -1202,7 +1074,6 @@ class DialogController extends AbstractController
$text_type = strtolower(trim(Request::input('text_type')));
$silence = in_array(strtolower(trim(Request::input('silence'))), ['yes', 'true', '1']);
$model_name = trim(Request::input('model_name'));
- $extra_data = Request::input('extra_data');
$markdown = in_array($text_type, ['md', 'markdown']);
//
$result = [];
@@ -1285,7 +1156,7 @@ class DialogController extends AbstractController
if ($model_name) {
$msgData['model_name'] = $model_name;
}
- $result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'longtext', $msgData, $user->userid, false, false, $silence, $key, $extra_data);
+ $result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'longtext', $msgData, $user->userid, false, false, $silence, $key);
} else {
$msgData = ['text' => $text];
if ($markdown) {
@@ -1294,7 +1165,7 @@ class DialogController extends AbstractController
if ($model_name) {
$msgData['model_name'] = $model_name;
}
- $result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence, $key, $extra_data);
+ $result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence, $key);
}
}
return $result;
diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php
index cf33a03f4..716429299 100644
--- a/app/Models/WebSocketDialogMsg.php
+++ b/app/Models/WebSocketDialogMsg.php
@@ -43,7 +43,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int|mixed $percentage
* @property-read \App\Models\User|null $user
- * @property-read \App\Models\WebSocketDialogMsgExtra|null $extra
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
@@ -112,14 +111,6 @@ class WebSocketDialogMsg extends AbstractModel
return $this->hasOne(User::class, 'userid', 'userid');
}
- /**
- * @return \Illuminate\Database\Eloquent\Relations\HasOne
- */
- public function extra(): \Illuminate\Database\Eloquent\Relations\HasOne
- {
- return $this->hasOne(WebSocketDialogMsgExtra::class, 'msg_id', 'id');
- }
-
/**
* 阅读占比
* @return int|mixed
@@ -1242,10 +1233,9 @@ class WebSocketDialogMsg extends AbstractModel
* @param bool|null $push_silence 推送-静默
* - type = [text|file|record|meeting] 默认为:false
* @param string|null $search_key 搜索关键词(用于搜索,留空则自动生成)
- * @param array|null $extra_data 额外数据(仅在发送消息时有效)
* @return array
*/
- public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null, $search_key = null, $extra_data = null)
+ public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null, $search_key = null)
{
$link = 0;
$mtype = $type;
@@ -1390,17 +1380,10 @@ class WebSocketDialogMsg extends AbstractModel
'msg' => $msg,
'read' => 0,
]);
- AbstractModel::transaction(function () use ($search_key, $dialogMsg, $extra_data) {
+ AbstractModel::transaction(function () use ($search_key, $dialogMsg) {
$dialogMsg->send = 1;
$dialogMsg->generateKeyAndSave($search_key);
//
- if ($extra_data) {
- WebSocketDialogMsgExtra::createInstance([
- 'msg_id' => $dialogMsg->id,
- 'data' => Base::array2json($extra_data),
- ])->save();
- }
- //
WebSocketDialogSession::updateTitle($dialogMsg->session_id, $dialogMsg);
//
if ($dialogMsg->type === 'meeting') {
diff --git a/app/Models/WebSocketDialogMsgExtra.php b/app/Models/WebSocketDialogMsgExtra.php
deleted file mode 100644
index 1d9ef32fc..000000000
--- a/app/Models/WebSocketDialogMsgExtra.php
+++ /dev/null
@@ -1,56 +0,0 @@
-belongsTo(WebSocketDialogMsg::class, 'msg_id', 'id');
- }
-}
-
diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php
index a501bd9c6..c3e172a20 100644
--- a/app/Tasks/BotReceiveMsgTask.php
+++ b/app/Tasks/BotReceiveMsgTask.php
@@ -66,7 +66,7 @@ class BotReceiveMsgTask extends AbstractTask
}
// 判断消息是否存在
- $msg = WebSocketDialogMsg::with(['user', 'extra'])->find($this->msgId);
+ $msg = WebSocketDialogMsg::with(['user'])->find($this->msgId);
if (empty($msg)) {
return;
}
@@ -523,16 +523,6 @@ class BotReceiveMsgTask extends AbstractTask
{$sendText}
EOF;
}
- // 处理额外数据
- if ($msg->extra && $msg->extra->data) {
- $extraData = $msg->extra->data;
- if ($extraData['system_prompt']) {
- $extras['system_message'] = $extraData['system_prompt'];
- }
- if ($extraData['context_prompt']) {
- $sendText = $extraData['context_prompt'] . $sendText;
- }
- }
$webhookUrl = "http://nginx/ai/chat";
} else {
// 用户机器人
diff --git a/database/migrations/2025_11_07_183944_create_web_socket_dialog_msg_extras_table.php b/database/migrations/2025_11_07_183944_create_web_socket_dialog_msg_extras_table.php
deleted file mode 100644
index 6b812ae88..000000000
--- a/database/migrations/2025_11_07_183944_create_web_socket_dialog_msg_extras_table.php
+++ /dev/null
@@ -1,35 +0,0 @@
-id();
- $table->unsignedBigInteger('msg_id')->nullable()->default(0)->comment('消息ID');
- $table->longText('data')->nullable()->comment('额外数据');
- $table->timestamps();
-
- $table->foreign('msg_id')->references('id')->on('web_socket_dialog_msgs')->onDelete('cascade');
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('web_socket_dialog_msg_extras');
- }
-}
diff --git a/resources/assets/js/components/AIAssistant.vue b/resources/assets/js/components/AIAssistant.vue
index 76313a8ee..5b0c569f2 100644
--- a/resources/assets/js/components/AIAssistant.vue
+++ b/resources/assets/js/components/AIAssistant.vue
@@ -37,7 +37,7 @@
-
+
{{ $L('生成中...') }}
@@ -93,8 +93,8 @@