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

1119 lines
52 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;
}
.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>