1119 lines
52 KiB
HTML
1119 lines
52 KiB
HTML
<!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> |