Files
ai-chat-system/templates/admin_v2/index.html

504 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI对话系统 v2.0 - 后台管理</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style>
:root { --sidebar-width: 240px; }
body { background: #f5f7fa; }
.sidebar { position: fixed; left: 0; top: 0; width: var(--sidebar-width); height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 0; }
.sidebar .logo { padding: 0 20px 30px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.sidebar .logo h4 { margin: 0; font-size: 18px; }
.sidebar .logo .version { font-size: 12px; color: rgba(255,255,255,0.7); }
.sidebar .nav-item { padding: 10px 20px; cursor: pointer; transition: all 0.2s; }
.sidebar .nav-item:hover { background: rgba(255,255,255,0.1); }
.sidebar .nav-item.active { background: rgba(255,255,255,0.2); border-left: 3px solid white; }
.sidebar .nav-item i { margin-right: 10px; }
.main-content { margin-left: var(--sidebar-width); padding: 30px; }
.page-header { margin-bottom: 30px; }
.page-header h2 { margin: 0; font-size: 24px; }
.card { border: none; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.card-header { background: white; border-bottom: 1px solid #eee; padding: 15px 20px; }
.btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; }
.btn-primary:hover { background: linear-gradient(135deg, #5a6fd6 0%, #6a4190 100%); }
.table th { background: #f8f9fa; font-weight: 600; }
.page-section { display: none; }
.page-section.active { display: block; }
.channel-web { border-left: 4px solid #17a2b8; }
.channel-matrix { border-left: 4px solid #28a745; }
.thinking-config { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 10px; }
</style>
</head>
<body>
<div class="sidebar">
<div class="logo">
<h4><i class="ri-robot-line"></i> AI对话系统</h4>
<div class="version">v2.0.0 后台管理</div>
</div>
<div class="nav mt-3">
<div class="nav-item active" data-page="llm-providers"><i class="ri-cloud-line"></i> 大模型池</div>
<div class="nav-item" data-page="agents"><i class="ri-robot-2-line"></i> Agent管理</div>
<div class="nav-item" data-page="channels"><i class="ri-chat-3-line"></i> 渠道管理</div>
<div class="nav-item" data-page="stats"><i class="ri-bar-chart-box-line"></i> 统计数据</div>
</div>
</div>
<div class="main-content">
<!-- 大模型池 -->
<div class="page-section active" id="page-llm-providers">
<div class="page-header"><h2><i class="ri-cloud-line"></i> 大模型池管理</h2></div>
<div class="card">
<div class="card-header d-flex justify-content-between">
<span>大模型提供商列表</span>
<button class="btn btn-primary btn-sm" onclick="showAddProviderModal()"><i class="ri-add-line"></i> 添加</button>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>名称</th><th>API地址</th><th>默认模型</th><th>思考支持</th><th>状态</th><th>操作</th></tr></thead>
<tbody id="providers-list"><tr><td colspan="6" class="text-center">加载中...</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- Agent -->
<div class="page-section" id="page-agents">
<div class="page-header"><h2><i class="ri-robot-2-line"></i> Agent管理</h2></div>
<div class="card">
<div class="card-header d-flex justify-content-between">
<span>Agent列表</span>
<button class="btn btn-primary btn-sm" onclick="showAddAgentModal()"><i class="ri-add-line"></i> 添加</button>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>名称</th><th>显示名</th><th>大模型池</th><th>思考</th><th>默认</th><th>状态</th><th>操作</th></tr></thead>
<tbody id="agents-list"><tr><td colspan="7" class="text-center">加载中...</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- 渠道 -->
<div class="page-section" id="page-channels">
<div class="page-header"><h2><i class="ri-chat-3-line"></i> 渠道管理</h2></div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card channel-web">
<div class="card-header"><i class="ri-global-line"></i> 网页端渠道</div>
<div class="card-body" id="web-channel-info">加载中...</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card channel-matrix">
<div class="card-header"><i class="ri-message-3-line"></i> Matrix渠道</div>
<div class="card-body" id="matrix-channel-info">加载中...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">渠道-Agent绑定关系</div>
<div class="card-body">
<table class="table">
<thead><tr><th>渠道</th><th>绑定Agent</th><th>优先级</th><th>模式</th><th>条件</th><th>操作</th></tr></thead>
<tbody id="mappings-list"><tr><td colspan="6" class="text-center">加载中...</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- 统计 -->
<div class="page-section" id="page-stats">
<div class="page-header"><h2><i class="ri-bar-chart-box-line"></i> 统计数据</h2></div>
<div class="row">
<div class="col-md-3 mb-4"><div class="card"><div class="card-body text-center"><i class="ri-user-line" style="font-size:40px;color:#667eea;"></i><h3 id="stat-users" class="mt-2">-</h3><p class="text-muted">用户数</p></div></div></div>
<div class="col-md-3 mb-4"><div class="card"><div class="card-body text-center"><i class="ri-chat-1-line" style="font-size:40px;color:#28a745;"></i><h3 id="stat-conversations" class="mt-2">-</h3><p class="text-muted">对话数</p></div></div></div>
<div class="col-md-3 mb-4"><div class="card"><div class="card-body text-center"><i class="ri-message-2-line" style="font-size:40px;color:#ffc107;"></i><h3 id="stat-messages" class="mt-2">-</h3><p class="text-muted">消息数</p></div></div></div>
<div class="col-md-3 mb-4"><div class="card"><div class="card-body text-center"><i class="ri-robot-line" style="font-size:40px;color:#17a2b8;"></i><h3 id="stat-agents" class="mt-2">-</h3><p class="text-muted">Agent数</p></div></div></div>
</div>
</div>
</div>
<!-- Provider Modal -->
<div class="modal fade" id="providerModal"><div class="modal-dialog modal-lg"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">添加/编辑大模型</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"><form id="provider-form">
<input type="hidden" id="provider-id">
<div class="row"><div class="col-md-6"><label class="form-label">名称 *</label><input type="text" class="form-control" id="provider-name" required></div><div class="col-md-6"><label class="form-label">默认模型</label><input type="text" class="form-control" id="provider-default-model"></div></div>
<div class="row mt-3"><div class="col-md-12"><label class="form-label">API地址 *</label><input type="text" class="form-control" id="provider-api-base" required></div></div>
<div class="row mt-3"><div class="col-md-12"><label class="form-label">API密钥</label><input type="text" class="form-control" id="provider-api-key"></div></div>
<div class="row mt-3"><div class="col-md-4"><label class="form-label">MaxTokens</label><input type="number" class="form-control" id="provider-max-tokens" value="4096"></div><div class="col-md-4"><label class="form-label">Temperature</label><input type="number" class="form-control" id="provider-temperature" value="0.7" step="0.1"></div><div class="col-md-4"><label class="form-label">优先级</label><input type="number" class="form-control" id="provider-priority" value="0"></div></div>
<div class="mt-3"><label class="form-label">描述</label><textarea class="form-control" id="provider-description" rows="2"></textarea></div>
<div class="mt-3 form-check"><input type="checkbox" class="form-check-input" id="provider-active" checked><label class="form-check-label">启用</label></div>
<hr><h6>思考功能</h6>
<div class="thinking-config"><div class="row"><div class="col-md-6 form-check"><input type="checkbox" class="form-check-input" id="provider-supports-thinking"><label class="form-check-label">支持原生思考</label></div><div class="col-md-6"><label class="form-label">思考模型名</label><input type="text" class="form-control" id="provider-thinking-model"></div></div></div>
<div class="mt-3"><button type="button" class="btn btn-outline-primary" onclick="fetchProviderModels()"><i class="ri-refresh-line"></i> 获取模型</button><button type="button" class="btn btn-outline-secondary" onclick="testProviderConnection()"><i class="ri-link"></i> 测试连接</button></div>
<div class="mt-2" id="provider-models-preview"></div><div class="mt-2" id="provider-test-result"></div>
</form></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="saveProvider()">保存</button></div>
</div></div></div>
<!-- Agent Modal -->
<div class="modal fade" id="agentModal"><div class="modal-dialog modal-lg"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">添加/编辑Agent</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"><form id="agent-form">
<input type="hidden" id="agent-id">
<div class="row"><div class="col-md-4"><label class="form-label">名称 *</label><input type="text" class="form-control" id="agent-name" required></div><div class="col-md-4"><label class="form-label">显示名</label><input type="text" class="form-control" id="agent-display-name"></div><div class="col-md-4"><label class="form-label">大模型池 *</label><select class="form-select" id="agent-llm-provider" required><option value="">请选择...</option></select></div></div>
<div class="row mt-3"><div class="col-md-6"><label class="form-label">模型覆盖</label><input type="text" class="form-control" id="agent-model-override"></div><div class="col-md-6"><label class="form-label">最大历史</label><input type="number" class="form-control" id="agent-max-history" value="20"></div></div>
<div class="mt-3"><label class="form-label">系统设定</label><textarea class="form-control" id="agent-system-prompt" rows="3">你是一个有用的AI助手。</textarea></div>
<div class="mt-3"><label class="form-label">描述</label><textarea class="form-control" id="agent-description" rows="2"></textarea></div>
<div class="row mt-3"><div class="col-md-4 form-check"><input type="checkbox" class="form-check-input" id="agent-active" checked><label class="form-check-label">启用</label></div><div class="col-md-4 form-check"><input type="checkbox" class="form-check-input" id="agent-default"><label class="form-check-label">默认</label></div><div class="col-md-4"><label class="form-label">温度覆盖</label><input type="number" class="form-control" id="agent-temperature-override" step="0.1"></div></div>
<hr><h6>思考功能</h6>
<div class="thinking-config"><div class="form-check"><input type="checkbox" class="form-check-input" id="agent-enable-thinking" checked><label class="form-check-label">启用思考</label></div><div class="mt-2"><label class="form-label">思考提示词</label><textarea class="form-control" id="agent-thinking-prompt" rows="2"></textarea></div><div class="row mt-2"><div class="col-md-6"><label class="form-label">前缀</label><input type="text" class="form-control" id="agent-thinking-prefix"></div><div class="col-md-6"><label class="form-label">后缀</label><input type="text" class="form-control" id="agent-thinking-suffix"></div></div></div>
</form></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="saveAgent()">保存</button></div>
</div></div></div>
<!-- Binding Modal -->
<div class="modal fade" id="bindingModal"><div class="modal-dialog"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">绑定Agent</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"><form id="binding-form">
<input type="hidden" id="binding-channel-id">
<div class="mb-3"><label class="form-label">选择Agent</label><select class="form-select" id="binding-agent-id" required><option value="">请选择...</option></select></div>
<div class="mb-3"><label class="form-label">优先级</label><input type="number" class="form-control" id="binding-priority" value="0"></div>
<div class="mb-3"><label class="form-label">模式</label><select class="form-select" id="binding-mode"><option value="single">single</option><option value="round_robin">轮询</option><option value="fallback">备用</option></select></div>
<div class="mb-3"><label class="form-label">条件(JSON)</label><textarea class="form-control" id="binding-conditions" rows="2"></textarea></div>
</form></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="saveBinding()">保存</button></div>
</div></div></div>
<!-- Matrix Config Modal -->
<div class="modal fade" id="matrixConfigModal"><div class="modal-dialog"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">Matrix配置</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"><form id="matrix-config-form">
<div class="mb-3"><label class="form-label">服务器地址</label><input type="text" class="form-control" id="matrix-homeserver"></div>
<div class="mb-3"><label class="form-label">Bot用户名</label><input type="text" class="form-control" id="matrix-username"></div>
<div class="mb-3"><label class="form-label">密码</label><input type="text" class="form-control" id="matrix-password"></div>
</form></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="saveMatrixConfig()">保存</button></div>
</div></div></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 全局数据
let providersData = [], agentsData = [], channelsData = [];
// 页面切换函数
function loadPageData(page) {
if (page === 'llm-providers') loadProviders();
if (page === 'agents') loadAgents();
if (page === 'channels') loadChannels();
if (page === 'stats') loadStats();
}
// ===== 大模型池 =====
async function loadProviders() {
const res = await fetch('/api/v2/providers');
const data = await res.json();
providersData = data.providers || [];
const tbody = document.getElementById('providers-list');
if (providersData.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">暂无数据</td></tr>';
return;
}
tbody.innerHTML = providersData.map(p => `<tr>
<td><strong>${p.name}</strong></td><td><small>${p.api_base||'-'}</small></td><td>${p.default_model||'auto'}</td>
<td>${p.supports_thinking?'<span class="badge bg-success">支持</span>':'<span class="badge bg-secondary">不支持</span>'}</td>
<td>${p.is_active?'<span class="badge bg-success">启用</span>':'<span class="badge bg-secondary">禁用</span>'}</td>
<td><button class="btn btn-sm btn-outline-primary" onclick="editProvider(${p.id})"><i class="ri-edit-line"></i></button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteProvider(${p.id},'${p.name}')"><i class="ri-delete-bin-line"></i></button></td>
</tr>`).join('');
updateProviderSelect();
}
function updateProviderSelect() {
const select = document.getElementById('agent-llm-provider');
select.innerHTML = '<option value="">请选择...</option>' + providersData.filter(p=>p.is_active).map(p=>`<option value="${p.id}">${p.name}</option>`).join('');
}
function showAddProviderModal() {
document.getElementById('provider-form').reset();
document.getElementById('provider-id').value = '';
document.getElementById('provider-active').checked = true;
document.getElementById('provider-models-preview').innerHTML = '';
document.getElementById('provider-test-result').innerHTML = '';
new bootstrap.Modal(document.getElementById('providerModal')).show();
}
function editProvider(id) {
const p = providersData.find(x => x.id === id);
if (!p) return;
document.getElementById('provider-id').value = id;
document.getElementById('provider-name').value = p.name;
document.getElementById('provider-api-base').value = p.api_base || '';
document.getElementById('provider-api-key').value = p.api_key || '';
document.getElementById('provider-default-model').value = p.default_model || '';
document.getElementById('provider-max-tokens').value = p.max_tokens || 4096;
document.getElementById('provider-temperature').value = p.temperature || 0.7;
document.getElementById('provider-priority').value = p.priority || 0;
document.getElementById('provider-description').value = p.description || '';
document.getElementById('provider-active').checked = p.is_active;
document.getElementById('provider-supports-thinking').checked = p.supports_thinking;
document.getElementById('provider-thinking-model').value = p.thinking_model || '';
new bootstrap.Modal(document.getElementById('providerModal')).show();
}
async function saveProvider() {
const id = document.getElementById('provider-id').value;
const data = {
name: document.getElementById('provider-name').value,
api_base: document.getElementById('provider-api-base').value,
api_key: document.getElementById('provider-api-key').value,
default_model: document.getElementById('provider-default-model').value || 'auto',
max_tokens: parseInt(document.getElementById('provider-max-tokens').value) || 4096,
temperature: parseFloat(document.getElementById('provider-temperature').value) || 0.7,
priority: parseInt(document.getElementById('provider-priority').value) || 0,
description: document.getElementById('provider-description').value,
is_active: document.getElementById('provider-active').checked,
supports_thinking: document.getElementById('provider-supports-thinking').checked,
thinking_model: document.getElementById('provider-thinking-model').value
};
const res = await fetch(id ? `/api/v2/providers/${id}` : '/api/v2/providers', { method: id ? 'PUT' : 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) });
const result = await res.json();
if (result.success) { bootstrap.Modal.getInstance(document.getElementById('providerModal')).hide(); loadProviders(); alert('保存成功'); }
else alert('失败: ' + result.message);
}
async function deleteProvider(id, name) {
if (!confirm(`确定删除 "${name}"`)) return;
const res = await fetch(`/api/v2/providers/${id}`, {method:'DELETE'});
const result = await res.json();
if (result.success) { loadProviders(); alert('删除成功'); }
else alert('失败: ' + result.message);
}
async function fetchProviderModels() {
const api_base = document.getElementById('provider-api-base').value;
if (!api_base) { alert('请先填写API地址'); return; }
document.getElementById('provider-models-preview').innerHTML = '获取中...';
const res = await fetch('/api/v2/providers/models', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({api_base, api_key: document.getElementById('provider-api-key').value}) });
const result = await res.json();
document.getElementById('provider-models-preview').innerHTML = result.models?.length > 0
? '<strong>可用模型:</strong> ' + result.models.map(m=>`<span class="badge bg-secondary">${m.id}</span>`).join(' ')
: '<span class="text-warning">无法获取</span>';
}
async function testProviderConnection() {
const api_base = document.getElementById('provider-api-base').value;
if (!api_base) { alert('请先填写API地址'); return; }
document.getElementById('provider-test-result').innerHTML = '测试中...';
const res = await fetch('/api/v2/providers/test', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({api_base, api_key: document.getElementById('provider-api-key').value, model: document.getElementById('provider-default-model').value||'auto'}) });
const result = await res.json();
document.getElementById('provider-test-result').innerHTML = result.success
? '<span class="text-success">✅ ' + result.message + '</span>'
: '<span class="text-danger">❌ ' + result.message + '</span>';
}
// ===== Agent =====
async function loadAgents() {
const res = await fetch('/api/v2/agents');
const data = await res.json();
agentsData = data.agents || [];
const tbody = document.getElementById('agents-list');
if (agentsData.length === 0) { tbody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">暂无数据</td></tr>'; return; }
tbody.innerHTML = agentsData.map(a => `<tr>
<td><strong>${a.name}</strong></td><td>${a.display_name||a.name}</td><td>${a.llm_provider_name||'-'}</td>
<td>${a.enable_thinking?'<span class="badge bg-success">开启</span>':'<span class="badge bg-secondary">关闭</span>'}</td>
<td>${a.is_default?'<span class="badge bg-warning">默认</span>':'-'}</td>
<td>${a.is_active?'<span class="badge bg-success">启用</span>':'<span class="badge bg-secondary">禁用</span>'}</td>
<td><button class="btn btn-sm btn-outline-primary" onclick="editAgent(${a.id})"><i class="ri-edit-line"></i></button>
${!a.is_default?`<button class="btn btn-sm btn-outline-warning" onclick="setDefaultAgent(${a.id})"><i class="ri-star-line"></i></button><button class="btn btn-sm btn-outline-danger" onclick="deleteAgent(${a.id},'${a.name}')"><i class="ri-delete-bin-line"></i></button>`:''}</td>
</tr>`).join('');
updateAgentSelect();
}
function updateAgentSelect() {
const select = document.getElementById('binding-agent-id');
select.innerHTML = '<option value="">请选择...</option>' + agentsData.filter(a=>a.is_active).map(a=>`<option value="${a.id}">${a.display_name||a.name}</option>`).join('');
}
function showAddAgentModal() {
document.getElementById('agent-form').reset();
document.getElementById('agent-id').value = '';
document.getElementById('agent-active').checked = true;
document.getElementById('agent-enable-thinking').checked = true;
document.getElementById('agent-system-prompt').value = '你是一个有用的AI助手。';
updateProviderSelect();
new bootstrap.Modal(document.getElementById('agentModal')).show();
}
function editAgent(id) {
const a = agentsData.find(x => x.id === id);
if (!a) return;
updateProviderSelect();
document.getElementById('agent-id').value = id;
document.getElementById('agent-name').value = a.name;
document.getElementById('agent-display-name').value = a.display_name || '';
document.getElementById('agent-llm-provider').value = a.llm_provider_id;
document.getElementById('agent-model-override').value = a.model_override || '';
document.getElementById('agent-max-history').value = a.max_history || 20;
document.getElementById('agent-system-prompt').value = a.system_prompt || '';
document.getElementById('agent-description').value = a.description || '';
document.getElementById('agent-active').checked = a.is_active;
document.getElementById('agent-default').checked = a.is_default;
document.getElementById('agent-temperature-override').value = a.temperature_override || '';
document.getElementById('agent-enable-thinking').checked = a.enable_thinking;
document.getElementById('agent-thinking-prompt').value = a.thinking_prompt || '';
document.getElementById('agent-thinking-prefix').value = a.thinking_prefix || '';
document.getElementById('agent-thinking-suffix').value = a.thinking_suffix || '';
new bootstrap.Modal(document.getElementById('agentModal')).show();
}
async function saveAgent() {
const id = document.getElementById('agent-id').value;
const data = {
name: document.getElementById('agent-name').value,
display_name: document.getElementById('agent-display-name').value,
llm_provider_id: parseInt(document.getElementById('agent-llm-provider').value),
model_override: document.getElementById('agent-model-override').value,
max_history: parseInt(document.getElementById('agent-max-history').value) || 20,
system_prompt: document.getElementById('agent-system-prompt').value,
description: document.getElementById('agent-description').value,
is_active: document.getElementById('agent-active').checked,
is_default: document.getElementById('agent-default').checked,
temperature_override: parseFloat(document.getElementById('agent-temperature-override').value) || null,
enable_thinking: document.getElementById('agent-enable-thinking').checked,
thinking_prompt: document.getElementById('agent-thinking-prompt').value,
thinking_prefix: document.getElementById('agent-thinking-prefix').value,
thinking_suffix: document.getElementById('agent-thinking-suffix').value
};
const res = await fetch(id ? `/api/v2/agents/${id}` : '/api/v2/agents', { method: id ? 'PUT' : 'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
const result = await res.json();
if (result.success) { bootstrap.Modal.getInstance(document.getElementById('agentModal')).hide(); loadAgents(); alert('保存成功'); }
else alert('失败: ' + result.message);
}
async function setDefaultAgent(id) {
const res = await fetch(`/api/v2/agents/${id}/default`, {method:'POST'});
const result = await res.json();
if (result.success) { loadAgents(); alert('已设为默认'); }
}
async function deleteAgent(id, name) {
if (!confirm(`确定删除 "${name}"`)) return;
const res = await fetch(`/api/v2/agents/${id}`, {method:'DELETE'});
const result = await res.json();
if (result.success) { loadAgents(); alert('删除成功'); }
else alert('失败: ' + result.message);
}
// ===== 渠道 =====
async function loadChannels() {
const res = await fetch('/api/v2/channels');
const data = await res.json();
channelsData = data.channels || [];
const mappings = data.mappings || [];
const webChannel = channelsData.find(c => c.channel_type === 'web');
const matrixChannel = channelsData.find(c => c.channel_type === 'matrix');
document.getElementById('web-channel-info').innerHTML = renderChannelInfo(webChannel, 'web');
document.getElementById('matrix-channel-info').innerHTML = renderChannelInfo(matrixChannel, 'matrix');
const tbody = document.getElementById('mappings-list');
if (mappings.length === 0) { tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">暂无绑定</td></tr>'; return; }
tbody.innerHTML = mappings.map(m => `<tr>
<td><span class="badge bg-info">${m.channel_name}</span></td><td>${m.agent_name}</td><td>${m.priority}</td>
<td><span class="badge bg-secondary">${m.mode}</span></td><td>${m.conditions?JSON.stringify(m.conditions):'-'}</td>
<td><button class="btn btn-sm btn-outline-danger" onclick="deleteMapping(${m.id})"><i class="ri-delete-bin-line"></i></button></td>
</tr>`).join('');
}
function renderChannelInfo(channel, type) {
if (!channel) return '<p class="text-muted">未配置</p>';
const agentsHtml = channel.agent_mappings?.length > 0
? channel.agent_mappings.map(m => `<span class="badge bg-secondary">${m.agent.display_name}</span>`).join(' ')
: '<span class="text-muted">未绑定</span>';
const configBtn = type === 'matrix'
? `<button class="btn btn-sm btn-outline-primary mt-2" onclick="editMatrixConfig(${channel.id})"><i class="ri-settings-3-line"></i> 配置</button>` : '';
return `<p><strong>名称:</strong> ${channel.name}</p><p><strong>状态:</strong> ${channel.is_active?'<span class="badge bg-success">启用</span>':'<span class="badge bg-secondary">禁用</span>'}</p>
<p><strong>绑定Agent:</strong> ${agentsHtml}</p><button class="btn btn-sm btn-primary" onclick="showBindAgentModal(${channel.id})">绑定Agent</button>${configBtn}`;
}
function showBindAgentModal(channelId) {
document.getElementById('binding-channel-id').value = channelId;
document.getElementById('binding-form').reset();
updateAgentSelect();
new bootstrap.Modal(document.getElementById('bindingModal')).show();
}
async function saveBinding() {
const channelId = document.getElementById('binding-channel-id').value;
const conditionsStr = document.getElementById('binding-conditions').value;
const data = {
channel_id: parseInt(channelId),
agent_id: parseInt(document.getElementById('binding-agent-id').value),
priority: parseInt(document.getElementById('binding-priority').value) || 0,
mode: document.getElementById('binding-mode').value,
conditions: conditionsStr ? JSON.parse(conditionsStr) : null
};
const res = await fetch('/api/v2/channels/bind', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
const result = await res.json();
if (result.success) { bootstrap.Modal.getInstance(document.getElementById('bindingModal')).hide(); loadChannels(); alert('绑定成功'); }
else alert('失败: ' + result.message);
}
async function deleteMapping(id) {
if (!confirm('确定解绑?')) return;
const res = await fetch(`/api/v2/channels/unbind/${id}`, {method:'DELETE'});
const result = await res.json();
if (result.success) { loadChannels(); alert('解绑成功'); }
}
function editMatrixConfig(channelId) {
const channel = channelsData.find(c => c.id === channelId);
if (!channel) return;
const config = channel.config || {};
document.getElementById('matrix-homeserver').value = config.homeserver || '';
document.getElementById('matrix-username').value = config.username || '';
document.getElementById('matrix-password').value = config.password || '';
document.getElementById('matrix-config-form').dataset.channelId = channelId;
new bootstrap.Modal(document.getElementById('matrixConfigModal')).show();
}
async function saveMatrixConfig() {
const channelId = document.getElementById('matrix-config-form').dataset.channelId;
const res = await fetch(`/api/v2/channels/${channelId}/config`, { method:'PUT', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ config: { homeserver: document.getElementById('matrix-homeserver').value, username: document.getElementById('matrix-username').value, password: document.getElementById('matrix-password').value } }) });
const result = await res.json();
if (result.success) { bootstrap.Modal.getInstance(document.getElementById('matrixConfigModal')).hide(); loadChannels(); alert('配置已更新'); }
}
// ===== 统计 =====
async function loadStats() {
const res = await fetch('/api/admin/stats');
const data = await res.json();
document.getElementById('stat-users').textContent = data.total_users || 0;
document.getElementById('stat-conversations').textContent = data.total_conversations || 0;
document.getElementById('stat-messages').textContent = data.total_messages || 0;
const agentsRes = await fetch('/api/v2/agents');
const agentsData = await agentsRes.json();
document.getElementById('stat-agents').textContent = (agentsData.agents || []).length;
}
// ===== 初始化 =====
document.addEventListener('DOMContentLoaded', function() {
// 页面切换事件绑定
document.querySelectorAll('.sidebar .nav-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.sidebar .nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
const page = item.dataset.page;
document.querySelectorAll('.page-section').forEach(s => s.classList.remove('active'));
document.getElementById('page-' + page).classList.add('active');
loadPageData(page);
});
});
// 初始加载
loadProviders();
loadStats();
});
</script>
</body>
</html>