迁移AI厂商后台事件
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
// AI 厂商后台事件代理,集中管理全局开关、厂商状态、默认厂商和连通性测试。
|
||||
|
||||
let adminAiProvidersControlsBound = false;
|
||||
|
||||
/**
|
||||
* 读取 CSRF 令牌,供后台 AJAX 请求使用。
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCsrfToken() {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用后台接口切换状态类请求。
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function postJson(url) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": getCsrfToken(),
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步聊天机器人总开关按钮与状态文案。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @param {boolean} enabled
|
||||
* @returns {void}
|
||||
*/
|
||||
function renderChatBotState(button, enabled) {
|
||||
const knob = button.firstElementChild;
|
||||
const statusText = document.getElementById("chatbot-status-text");
|
||||
|
||||
button.classList.toggle("bg-emerald-500", enabled);
|
||||
button.classList.toggle("bg-gray-300", !enabled);
|
||||
knob?.classList.toggle("translate-x-6", enabled);
|
||||
knob?.classList.toggle("translate-x-1", !enabled);
|
||||
|
||||
if (statusText) {
|
||||
statusText.textContent = enabled ? "已开启" : "已关闭";
|
||||
statusText.classList.toggle("text-emerald-600", enabled);
|
||||
statusText.classList.toggle("text-gray-400", !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换全局聊天机器人开关。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function toggleChatBot(button) {
|
||||
try {
|
||||
const toggleUrl = button.getAttribute("data-ai-chatbot-toggle-url");
|
||||
if (!toggleUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await postJson(toggleUrl);
|
||||
if (data.status === "success") {
|
||||
renderChatBotState(button, Boolean(data.enabled));
|
||||
window.alert(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(`操作失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换厂商启用状态,成功后刷新列表。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function toggleProvider(button) {
|
||||
try {
|
||||
const toggleUrl = button.getAttribute("data-ai-provider-toggle-url");
|
||||
if (!toggleUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await postJson(toggleUrl);
|
||||
if (data.status === "success") {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(`操作失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前厂商设为默认,成功后刷新列表。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function setDefaultProvider(button) {
|
||||
try {
|
||||
const defaultUrl = button.getAttribute("data-ai-provider-default-url");
|
||||
if (!defaultUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await postJson(defaultUrl);
|
||||
if (data.status === "success") {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(`操作失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 AI 厂商接口连通性,并恢复按钮状态。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function testProviderConnection(button) {
|
||||
const testUrl = button.getAttribute("data-ai-provider-test-url");
|
||||
const providerName = button.getAttribute("data-ai-provider-name") ?? "AI 厂商";
|
||||
if (!testUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalText = button.textContent;
|
||||
button.textContent = "测试中…";
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
const data = await postJson(testUrl);
|
||||
if (data.ok) {
|
||||
window.alert(`✅ 「${providerName}」 连通成功!\n⤵ 响应耗时:${data.ms}ms(包含冷启动)\n🤖 模型回复:${data.message}`);
|
||||
} else {
|
||||
window.alert(`❌ 「${providerName}」 连通失败!\n耗时:${data.ms}ms\n错误:${data.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(`请求异常:${error.message}`);
|
||||
} finally {
|
||||
button.textContent = originalText;
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 AI 厂商后台页操作。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function bindAdminAiProvidersControls() {
|
||||
if (adminAiProvidersControlsBound || typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
adminAiProvidersControlsBound = true;
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatBotToggle = event.target.closest("[data-ai-chatbot-toggle-url]");
|
||||
if (chatBotToggle instanceof HTMLButtonElement) {
|
||||
event.preventDefault();
|
||||
void toggleChatBot(chatBotToggle);
|
||||
return;
|
||||
}
|
||||
|
||||
const providerToggle = event.target.closest("[data-ai-provider-toggle-url]");
|
||||
if (providerToggle instanceof HTMLButtonElement) {
|
||||
event.preventDefault();
|
||||
void toggleProvider(providerToggle);
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultButton = event.target.closest("[data-ai-provider-default-url]");
|
||||
if (defaultButton instanceof HTMLButtonElement) {
|
||||
event.preventDefault();
|
||||
void setDefaultProvider(defaultButton);
|
||||
return;
|
||||
}
|
||||
|
||||
const testButton = event.target.closest("[data-ai-provider-test-url]");
|
||||
if (testButton instanceof HTMLButtonElement) {
|
||||
event.preventDefault();
|
||||
void testProviderConnection(testButton);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import './bootstrap';
|
||||
import { bindAdminAiProvidersControls } from './admin/ai-providers.js';
|
||||
import { bindAdminAutoactControls } from './admin/autoact.js';
|
||||
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
||||
import { bindAdminFormConfirmations } from './admin/form-confirmations.js';
|
||||
@@ -10,6 +11,7 @@ import { bindAdminRoomControls } from './admin/rooms.js';
|
||||
import { bindAdminSignInRulesControls } from './admin/sign-in-rules.js';
|
||||
|
||||
// 后台共用入口只注册轻量事件代理,具体页面通过 data-* 属性决定是否响应。
|
||||
bindAdminAiProvidersControls();
|
||||
bindAdminAutoactControls();
|
||||
bindAdminFishingEventsControls();
|
||||
bindAdminFormConfirmations();
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<h2 class="text-lg font-bold text-gray-800">🤖 AI 聊天机器人配置</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-500">大厅状态:</span>
|
||||
<button id="chatbot-toggle-btn" onclick="toggleChatBot()"
|
||||
<button id="chatbot-toggle-btn" data-ai-chatbot-toggle-url="{{ route('admin.ai-providers.toggle-chatbot') }}"
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {{ $chatbotEnabled ? 'bg-emerald-500' : 'bg-gray-300' }}">
|
||||
<span
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {{ $chatbotEnabled ? 'translate-x-6' : 'translate-x-1' }}"></span>
|
||||
@@ -182,7 +182,7 @@
|
||||
{{ $provider->sort_order }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<button onclick="toggleProvider({{ $provider->id }}, this)"
|
||||
<button data-ai-provider-toggle-url="{{ route('admin.ai-providers.toggle', $provider->id) }}"
|
||||
class="px-3 py-1 rounded-full text-xs font-bold {{ $provider->is_enabled ? 'bg-emerald-100 text-emerald-700' : 'bg-gray-100 text-gray-500' }}">
|
||||
{{ $provider->is_enabled ? '已启用' : '已禁用' }}
|
||||
</button>
|
||||
@@ -192,7 +192,7 @@
|
||||
<span class="px-3 py-1 rounded-full text-xs font-bold bg-amber-100 text-amber-700">★
|
||||
默认</span>
|
||||
@else
|
||||
<button onclick="setDefault({{ $provider->id }})"
|
||||
<button data-ai-provider-default-url="{{ route('admin.ai-providers.default', $provider->id) }}"
|
||||
class="px-3 py-1 rounded-full text-xs text-gray-400 hover:text-amber-600 hover:bg-amber-50 transition">
|
||||
设为默认
|
||||
</button>
|
||||
@@ -200,13 +200,13 @@
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button
|
||||
onclick="testConnection({{ $provider->id }}, '{{ addslashes($provider->name) }}')"
|
||||
<button data-ai-provider-test-url="{{ route('admin.ai-providers.test', $provider->id) }}"
|
||||
data-ai-provider-name="{{ $provider->name }}"
|
||||
class="text-teal-600 hover:text-teal-800 text-xs font-bold">⚡ 测试</button>
|
||||
<button x-on:click="openEdit({{ $provider->toJson() }})"
|
||||
class="text-indigo-600 hover:text-indigo-800 text-xs font-bold">编辑</button>
|
||||
<form action="{{ route('admin.ai-providers.destroy', $provider->id) }}" method="POST"
|
||||
onsubmit="return confirm('确定要删除 {{ $provider->name }} 吗?')">
|
||||
data-admin-confirm="确定要删除 {{ $provider->name }} 吗?">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
@@ -328,117 +328,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 切换全局聊天机器人开关
|
||||
*/
|
||||
async function toggleChatBot() {
|
||||
try {
|
||||
const res = await fetch('{{ route('admin.ai-providers.toggle-chatbot') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
// 更新按钮样式
|
||||
const btn = document.getElementById('chatbot-toggle-btn');
|
||||
const text = document.getElementById('chatbot-status-text');
|
||||
if (data.enabled) {
|
||||
btn.className = btn.className.replace('bg-gray-300', 'bg-emerald-500');
|
||||
btn.firstElementChild.className = btn.firstElementChild.className.replace('translate-x-1',
|
||||
'translate-x-6');
|
||||
text.textContent = '已开启';
|
||||
text.className = text.className.replace('text-gray-400', 'text-emerald-600');
|
||||
} else {
|
||||
btn.className = btn.className.replace('bg-emerald-500', 'bg-gray-300');
|
||||
btn.firstElementChild.className = btn.firstElementChild.className.replace('translate-x-6',
|
||||
'translate-x-1');
|
||||
text.textContent = '已关闭';
|
||||
text.className = text.className.replace('text-emerald-600', 'text-gray-400');
|
||||
}
|
||||
alert(data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换厂商启用/禁用状态
|
||||
*/
|
||||
async function toggleProvider(id, btn) {
|
||||
try {
|
||||
const res = await fetch('/admin/ai-providers/' + id + '/toggle', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 AI 接口连通性
|
||||
*/
|
||||
async function testConnection(id, name) {
|
||||
const btn = event.target;
|
||||
const origText = btn.textContent;
|
||||
btn.textContent = '测试中…';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/ai-providers/' + id + '/test', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
alert(`✅ 「${name}」 连通成功!\n⤵ 响应耗时:${data.ms}ms(包含冷启动)\n🤖 模型回复:${data.message}`);
|
||||
} else {
|
||||
alert(`❌ 「${name}」 连通失败!\n耗时:${data.ms}ms\n错误:${data.message}`);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('请求异常:' + e.message);
|
||||
} finally {
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设为默认 AI 厂商
|
||||
*/
|
||||
async function setDefault(id) {
|
||||
try {
|
||||
const res = await fetch('/admin/ai-providers/' + id + '/default', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user