迁移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 './bootstrap';
|
||||||
|
import { bindAdminAiProvidersControls } from './admin/ai-providers.js';
|
||||||
import { bindAdminAutoactControls } from './admin/autoact.js';
|
import { bindAdminAutoactControls } from './admin/autoact.js';
|
||||||
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
||||||
import { bindAdminFormConfirmations } from './admin/form-confirmations.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';
|
import { bindAdminSignInRulesControls } from './admin/sign-in-rules.js';
|
||||||
|
|
||||||
// 后台共用入口只注册轻量事件代理,具体页面通过 data-* 属性决定是否响应。
|
// 后台共用入口只注册轻量事件代理,具体页面通过 data-* 属性决定是否响应。
|
||||||
|
bindAdminAiProvidersControls();
|
||||||
bindAdminAutoactControls();
|
bindAdminAutoactControls();
|
||||||
bindAdminFishingEventsControls();
|
bindAdminFishingEventsControls();
|
||||||
bindAdminFormConfirmations();
|
bindAdminFormConfirmations();
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<h2 class="text-lg font-bold text-gray-800">🤖 AI 聊天机器人配置</h2>
|
<h2 class="text-lg font-bold text-gray-800">🤖 AI 聊天机器人配置</h2>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm text-gray-500">大厅状态:</span>
|
<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' }}">
|
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {{ $chatbotEnabled ? 'bg-emerald-500' : 'bg-gray-300' }}">
|
||||||
<span
|
<span
|
||||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {{ $chatbotEnabled ? 'translate-x-6' : 'translate-x-1' }}"></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 }}
|
{{ $provider->sort_order }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-center">
|
<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' }}">
|
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 ? '已启用' : '已禁用' }}
|
{{ $provider->is_enabled ? '已启用' : '已禁用' }}
|
||||||
</button>
|
</button>
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
<span class="px-3 py-1 rounded-full text-xs font-bold bg-amber-100 text-amber-700">★
|
<span class="px-3 py-1 rounded-full text-xs font-bold bg-amber-100 text-amber-700">★
|
||||||
默认</span>
|
默认</span>
|
||||||
@else
|
@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">
|
class="px-3 py-1 rounded-full text-xs text-gray-400 hover:text-amber-600 hover:bg-amber-50 transition">
|
||||||
设为默认
|
设为默认
|
||||||
</button>
|
</button>
|
||||||
@@ -200,13 +200,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-center">
|
<td class="px-6 py-4 text-center">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button data-ai-provider-test-url="{{ route('admin.ai-providers.test', $provider->id) }}"
|
||||||
onclick="testConnection({{ $provider->id }}, '{{ addslashes($provider->name) }}')"
|
data-ai-provider-name="{{ $provider->name }}"
|
||||||
class="text-teal-600 hover:text-teal-800 text-xs font-bold">⚡ 测试</button>
|
class="text-teal-600 hover:text-teal-800 text-xs font-bold">⚡ 测试</button>
|
||||||
<button x-on:click="openEdit({{ $provider->toJson() }})"
|
<button x-on:click="openEdit({{ $provider->toJson() }})"
|
||||||
class="text-indigo-600 hover:text-indigo-800 text-xs font-bold">编辑</button>
|
class="text-indigo-600 hover:text-indigo-800 text-xs font-bold">编辑</button>
|
||||||
<form action="{{ route('admin.ai-providers.destroy', $provider->id) }}" method="POST"
|
<form action="{{ route('admin.ai-providers.destroy', $provider->id) }}" method="POST"
|
||||||
onsubmit="return confirm('确定要删除 {{ $provider->name }} 吗?')">
|
data-admin-confirm="确定要删除 {{ $provider->name }} 吗?">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@@ -328,117 +328,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
@endsection
|
||||||
|
|||||||
Reference in New Issue
Block a user