diff --git a/app/admin/controller/mall/Goods.php b/app/admin/controller/mall/Goods.php index 078f75d..0227373 100644 --- a/app/admin/controller/mall/Goods.php +++ b/app/admin/controller/mall/Goods.php @@ -11,6 +11,8 @@ use app\admin\service\annotation\NodeAnnotation; use app\Request; use think\App; use think\response\Json; +use Wolfcode\Ai\Enum\AiType; +use Wolfcode\Ai\Service\AiChatService; #[ControllerAnnotation(title: '商城商品管理')] class Goods extends AdminController @@ -72,4 +74,61 @@ class Goods extends AdminController { return '这里演示方法不需要经过登录验证'; } + + + #[NodeAnnotation(title: 'AI优化', auth: true)] + public function aiOptimization(Request $request): void + { + // 演示环境下 默认返回的内容 + if ($this->isDemo) { + $content = << [ + 'role' => 'assistant', + 'content' => $content, + ]]]; + $this->success('success', compact('choices')); + } + + $message = $request->post('message'); + if (empty($message)) $this->error('请输入内容'); + try { + $result = AiChatService::instance() + // 当使用推理模型时,可能存在超时的情况,所以需要设置超时时间为 0 + // ->setTimeLimit(0) + // 请替换为您需要的模型类型 + ->setAiType(AiType::QWEN) + // 如果需要指定模型的 API 地址,可自行设置 + // ->setAiUrl('https://xxx.com') + // 请替换为您的模型 + ->setAiModel('qwen-plus') + // 请替换为您的 API KEY + ->setAiKey('sk-1234567890') + // 此内容会作为系统提示,会影响到回答的内容 当前仅作为测试使用 + ->setSystemContent('你现在是一位资深的海外电商产品经理') + ->chat($message); + $choices = $result['choices']; + }catch (\Throwable $exception) { + $choices = [['message' => [ + 'role' => 'assistant', + 'content' => $exception->getMessage(), + ]]]; + } + $this->success('success', compact('choices')); + } + } \ No newline at end of file diff --git a/app/admin/middleware/CheckAuth.php b/app/admin/middleware/CheckAuth.php index 08cc4dc..abd934a 100644 --- a/app/admin/middleware/CheckAuth.php +++ b/app/admin/middleware/CheckAuth.php @@ -35,7 +35,7 @@ class CheckAuth !$check && $this->error('无权限访问'); // 判断是否为演示环境 if (env('EASYADMIN.IS_DEMO', false) && $request->isPost()) { - if (!in_array($currentNode, ['system.log/record', ''])) $this->error('演示环境下不允许修改'); + if (!in_array($currentNode, ['system.log/record', 'mall.goods/aiOptimization'])) $this->error('演示环境下不允许修改'); } } return $next($request); diff --git a/app/admin/view/mall/goods/add.html b/app/admin/view/mall/goods/add.html index 26ae9bf..eb50666 100644 --- a/app/admin/view/mall/goods/add.html +++ b/app/admin/view/mall/goods/add.html @@ -26,9 +26,18 @@
- -
- +
+ +
+
+
+ +
+
+
+ +
+
diff --git a/app/admin/view/mall/goods/edit.html b/app/admin/view/mall/goods/edit.html index 8117f9c..44232e3 100644 --- a/app/admin/view/mall/goods/edit.html +++ b/app/admin/view/mall/goods/edit.html @@ -26,12 +26,22 @@
- -
- +
+ +
+
+
+ +
+
+
+ +
+
+
diff --git a/composer.json b/composer.json index 33bbe22..843f166 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "wolf-leo/phplogviewer": "^0.11.3", "wolfcode/authenticator": "^0.0.6", "wolfcode/rate-limiting": "^0.1.0", + "wolfcode/php-ai": "^0.1.2", "ext-json": "*", "ext-mysqli": "*", "ext-pdo": "*" diff --git a/public/static/admin/js/mall/goods.js b/public/static/admin/js/mall/goods.js index bc6e6f9..433752f 100644 --- a/public/static/admin/js/mall/goods.js +++ b/public/static/admin/js/mall/goods.js @@ -85,13 +85,55 @@ define(["jquery", "easy-admin"], function ($, ea) { ea.listen(); }, add: function () { + layui.util.on({ + AiOptimization: function (data) { + let layOn = $(data).attr('lay-on') + $(data).attr('lay-on', layOn + 'Loading') + aiOptimization(data) + }, + }) ea.listen(); }, edit: function () { + layui.util.on({ + AiOptimization: function (data) { + let layOn = $(data).attr('lay-on') + $(data).attr('lay-on', layOn + 'Loading') + aiOptimization(data) + }, + }) ea.listen(); }, stock: function () { ea.listen(); }, }; + + function aiOptimization(data) { + let layOn = $(data).attr('lay-on') + let title = $('input[name="title"]').val() + + // 告诉AI 你需要做什么 + let message = `优化这个标题 ${title}` + + if ($.trim(title) === '') { + ea.msg.error('标题不能为空', function () { + $(data).attr('lay-on', layOn.split('Loading')[0]) + }) + return false + } + let url = ea.url('mall.goods/aiOptimization') + ea.request.post({url: url, data: {message: message}}, function (res) { + let content = res.data?.choices[0]?.message?.content + // stream 为true 时,AI 内容会逐字输出 + let stream = true + ea.ai.chat(content, {stream: stream}, function () { + $(data).attr('lay-on', layOn.split('Loading')[0]) + }) + }, function (error) { + ea.msg.error(error.msg, function () { + $(data).attr('lay-on', layOn.split('Loading')[0]) + }) + }) + } }); \ No newline at end of file diff --git a/public/static/plugs/easy-admin/easy-admin.js b/public/static/plugs/easy-admin/easy-admin.js index c30079a..5739eb9 100644 --- a/public/static/plugs/easy-admin/easy-admin.js +++ b/public/static/plugs/easy-admin/easy-admin.js @@ -1761,6 +1761,60 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel } }, }, + ai: { + chat: function (content, options, cancel) { + let id = 'chat_' + (new Date()).getTime() + layer.open({ + 'title': options?.title || 'AI建议', + type: 1, + area: options?.area || ['42%', '60%'], + shade: options?.shade || 0, + shadeClose: options?.shadeClose || false, + scrollbar: options?.scrollbar || false, + maxmin: options?.maxmin || true, + anim: options?.anim || 0, + content: `
`, + success: function (layero, index) { + let elem = document.getElementById(id) + if (options?.stream) { + clearTimeout(aiStreamTimeout) + aiStreamCurrentIndex = 0 + setTimeout(() => { + admin.ai.streamOutput(elem, content) + }, 300) + } else { + content = content.replace(/\r\n/g, '
').replace(/\n/g, '
') + setTimeout(() => { + elem.innerHTML = content + }, 100) + } + }, + cancel: function (index, layero) { + cancel() + } + }) + }, + streamOutput: function (dom, htmlContent) { + const chunkSize = 1; + let length = htmlContent.length; + if (aiStreamCurrentIndex < length) { + const endIndex = Math.min(aiStreamCurrentIndex + chunkSize, length); + const chunk = htmlContent.slice(aiStreamCurrentIndex, endIndex); + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = chunk; + while (tempDiv.firstChild) { + dom.appendChild(tempDiv.firstChild); + } + aiStreamCurrentIndex = endIndex; + aiStreamTimeout = setTimeout(() => { + admin.ai.streamOutput(dom, htmlContent); + }, 60); + } + } + }, }; + var aiStreamCurrentIndex = 0; + var aiStreamTimeout = null; + return admin; });