Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a3ddef8b | ||
|
|
51f2cbc0f4 | ||
|
|
8acf9f3f6c |
@@ -14,7 +14,7 @@ DB_PORT=3306
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_PREFIX=ea8_
|
||||
|
||||
# 限流器开关
|
||||
# 限流器开关 若启动需要配置 Redis 服务
|
||||
RATE_LIMITING_STATUS=false
|
||||
|
||||
# Redis配置
|
||||
|
||||
@@ -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,62 @@ class Goods extends AdminController
|
||||
{
|
||||
return '这里演示方法不需要经过登录验证';
|
||||
}
|
||||
|
||||
|
||||
#[NodeAnnotation(title: 'AI优化', auth: true)]
|
||||
public function aiOptimization(Request $request): void
|
||||
{
|
||||
$message = $request->post('message');
|
||||
if (empty($message)) $this->error('请输入内容');
|
||||
|
||||
// 演示环境下 默认返回的内容
|
||||
if ($this->isDemo) {
|
||||
$content = <<<EOF
|
||||
演示环境中 默认返回的内容
|
||||
|
||||
我来帮你优化这个标题,让它更有吸引力且更符合电商平台的搜索逻辑:
|
||||
|
||||
"商务男士高端定制马克杯 | 办公室精英必备 | 优质陶瓷防烫手柄"
|
||||
|
||||
这个优化后的标题:
|
||||
1. 突出了目标用户群体(商务男士)
|
||||
2. 强调了产品定位(高端定制)
|
||||
3. 点明了使用场景(办公室)
|
||||
4. 添加了材质和功能特点(优质陶瓷、防烫手柄)
|
||||
5. 使用了吸引人的关键词(精英必备)
|
||||
|
||||
这样的标题不仅更具体,也更容易被搜索引擎识别,同时能精准触达目标客户群。您觉得这个版本如何?
|
||||
EOF;
|
||||
$choices = [['message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => $content,
|
||||
]]];
|
||||
$this->success('success', compact('choices'));
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -26,9 +26,18 @@
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
|
||||
<div class="layui-row">
|
||||
<label class="layui-form-label required">商品标题</label>
|
||||
<div class="layui-input-block layui-col-space5">
|
||||
<div class="layui-col-xs10">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs2">
|
||||
<button class="layui-btn layui-bg-purple layui-btn-fluid" type="button" lay-on="AiOptimization">AI优化</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,12 +26,22 @@
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
|
||||
<div class="layui-row">
|
||||
<label class="layui-form-label required">商品标题</label>
|
||||
<div class="layui-input-block layui-col-space5">
|
||||
<div class="layui-col-xs10">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs2">
|
||||
<button class="layui-btn layui-bg-purple layui-btn-fluid" type="button" lay-on="AiOptimization">AI优化</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">商品LOGO</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
|
||||
@@ -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": "*"
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -1761,6 +1761,61 @@ 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: `<div style="padding: 20px;white-space: pre-wrap;" id="${id}"></div>`,
|
||||
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, '<br>').replace(/\n/g, '<br>')
|
||||
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);
|
||||
dom.scrollIntoView({behavior: "smooth", block: "end"});
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
var aiStreamCurrentIndex = 0;
|
||||
var aiStreamTimeout = null;
|
||||
|
||||
return admin;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user