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

1119 lines
52 KiB
HTML
Raw Normal View History

<!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;
}
.badge-active {
background: #28a745;
}
.badge-inactive {
background: #6c757d;
}
.badge-default {
background: #ffc107;
color: #212529;
}
.thinking-toggle {
width: 50px;
}
.thinking-toggle .form-check-input {
width: 40px;
height: 20px;
}
.page-section {
display: none;
}
.page-section.active {
display: block;
}
/* Agent 卡片样式 */
.agent-card {
border-left: 4px solid #667eea;
}
.agent-card.default-agent {
border-left-color: #ffc107;
}
/* 渠道样式 */
.channel-web {
border-left: 4px solid #17a2b8;
}
.channel-matrix {
border-left: 4px solid #28a745;
}
/* Modal 表单样式 */
.modal-body .form-label {
font-weight: 600;
margin-top: 10px;
}
.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 class="nav-item" data-page="config">
<i class="ri-settings-3-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>
<p class="text-muted">配置多个大模型API,供Agent选择使用</p>
</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>
<p class="text-muted">配置AI智能体,设定系统提示和思考功能</p>
</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> 添加Agent
</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>
<p class="text-muted">管理网页端、Matrix等渠道,绑定Agent</p>
</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 class="page-section" id="page-config">
<div class="page-header">
<h2><i class="ri-settings-3-line"></i> 系统配置</h2>
</div>
<div class="card">
<div class="card-body">
<p class="text-muted">系统级别的通用配置...</p>
</div>
</div>
</div>
</div>
<!-- 添加大模型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 placeholder="如: DeepSeek, 本地LLM">
</div>
<div class="col-md-6">
<label class="form-label">默认模型</label>
<input type="text" class="form-control" id="provider-default-model" placeholder="auto">
</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 placeholder="http://xxx:port/v1">
</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" placeholder="sk-xxx">
</div>
</div>
<div class="row mt-3">
<div class="col-md-4">
<label class="form-label">Max Tokens</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" placeholder="如果有单独的思考模型">
</div>
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-primary" onclick="fetchProviderModels()">
<i class="ri-refresh-line"></i> 从API获取模型列表
</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>
<!-- 添加AgentModal -->
<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 placeholder="如: assistant, coder">
</div>
<div class="col-md-4">
<label class="form-label">显示名称</label>
<input type="text" class="form-control" id="agent-display-name" placeholder="默认助手">
</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" placeholder="覆盖Provider默认模型">
</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">系统设定(System Prompt</label>
<textarea class="form-control" id="agent-system-prompt" rows="4">你是一个有用的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" placeholder="可选">
</div>
</div>
<hr>
<h6>思考功能配置</h6>
<div class="thinking-config">
<div class="form-check mb-2">
<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" placeholder="请先仔细思考这个问题..."></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" placeholder="如: ">
</div>
<div class="col-md-6">
<label class="form-label">思考后缀标识</label>
<input type="text" class="form-control" id="agent-thinking-suffix" placeholder="如: ">
</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>
<!-- 绑定AgentModal -->
<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">
<small class="text-muted">数字越小优先级越高</small>
</div>
<div class="mb-3">
<label class="form-label">模式</label>
<select class="form-select" id="binding-mode">
<option value="single">single - 单Agent</option>
<option value="round_robin">round_robin - 轮询</option>
<option value="fallback">fallback - 备用</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">条件(JSON</label>
<textarea class="form-control" id="binding-conditions" rows="2" placeholder='{"user_level": "vip"}'></textarea>
<small class="text-muted">可选,满足条件时才使用此Agent</small>
</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配置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" placeholder="http://matrix.tphai.com">
</div>
<div class="mb-3">
<label class="form-label">Bot用户名</label>
<input type="text" class="form-control" id="matrix-username" placeholder="@ai-bot:matrix.tphai.com">
</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>
// 页面切换
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);
});
});
function loadPageData(page) {
if (page === 'llm-providers') loadProviders();
if (page === 'agents') loadAgents();
if (page === 'channels') loadChannels();
if (page === 'stats') loadStats();
}
// 初始加载
loadProviders();
loadStats();
// ===== 大模型池 =====
let providersData = [];
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>}
${p.thinking_model ? `<small class="text-muted">(${p.thinking_model})</small>` : ''}
</td>
<td>${p.is_active ? '<span class="badge badge-active">启用</span>' : '<span class="badge badge-inactive">禁用</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('');
// 更新Agent选择框
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} (${p.default_model || 'auto'})</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 url = id ? `/api/v2/providers/${id}` : '/api/v2/providers';
const method = id ? 'PUT' : 'POST';
const res = await fetch(url, {
method: method,
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}"?\n注意:如果有Agent使用此模型,删除将失败。`)) 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;
const api_key = document.getElementById('provider-api-key').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})
});
const result = await res.json();
if (result.models && result.models.length > 0) {
document.getElementById('provider-models-preview').innerHTML =
'<strong>可用模型:</strong> ' + result.models.map(m => `<span class="badge bg-secondary">${m.id}</span>`).join(' ');
} else {
document.getElementById('provider-models-preview').innerHTML =
'<span class="text-warning">无法获取模型列表</span>';
}
}
async function testProviderConnection() {
const api_base = document.getElementById('provider-api-base').value;
const api_key = document.getElementById('provider-api-key').value;
const model = document.getElementById('provider-default-model').value || 'auto';
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, model})
});
const result = await res.json();
if (result.success) {
document.getElementById('provider-test-result').innerHTML =
'<span class="text-success"><i class="ri-check-line"></i> ' + result.message + '</span>';
} else {
document.getElementById('provider-test-result').innerHTML =
'<span class="text-danger"><i class="ri-close-line"></i> ' + result.message + '</span>';
}
}
// ===== Agent =====
let agentsData = [];
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 class="${a.is_default ? 'default-agent' : ''}">
<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 badge-default">默认</span>' : '-'}</td>
<td>${a.is_active ? '<span class="badge badge-active">启用</span>' : '<span class="badge badge-inactive">禁用</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>` : ''}
${!a.is_default ? `<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 url = id ? `/api/v2/agents/${id}` : '/api/v2/agents';
const method = id ? 'PUT' : 'POST';
const res = await fetch(url, {
method: method,
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('已设为默认Agent');
}
}
async function deleteAgent(id, name) {
if (!confirm(`确定删除Agent "${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);
}
}
// ===== 渠道 =====
let channelsData = [];
let mappingsData = [];
async function loadChannels() {
const res = await fetch('/api/v2/channels');
const data = await res.json();
channelsData = data.channels || [];
mappingsData = 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 (mappingsData.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">暂无绑定</td></tr>';
return;
}
tbody.innerHTML = mappingsData.map(m => `
<tr>
<td><span class="badge bg-${m.channel_type === 'web' ? 'info' : 'success'}">${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>
<button class="btn btn-sm btn-primary" onclick="createChannel('${type}')">创建渠道</button>`;
}
const agentsHtml = channel.agent_mappings && 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">未绑定Agent</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> 配置Matrix
</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('确定解绑此Agent')) 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 || '';
// 保存channel ID用于更新
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('Matrix配置已更新');
}
}
// ===== 统计 =====
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;
// Agent数量
const agentsRes = await fetch('/api/v2/agents');
const agentsData = await agentsRes.json();
document.getElementById('stat-agents').textContent = (agentsData.agents || []).length;
}
</script>
</body>
</html>