agent / templates /index.html
samlax12's picture
Update templates/index.html
6972bfb verified
raw
history blame contribute delete
132 kB
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<title>教育AI助手开发平台</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsPlumb/2.15.6/js/jsplumb.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
/* 优雅的配色方案 */
--primary-color: #0f2d49;
--primary-light: #234a70;
--secondary-color: #4a6cfd;
--secondary-light: #7b91ff;
--tertiary-color: #f7f9fe;
--success-color: #10b981;
--success-light: rgba(16, 185, 129, 0.1);
--warning-color: #f59e0b;
--warning-light: rgba(245, 158, 11, 0.1);
--info-color: #0ea5e9;
--info-light: rgba(14, 165, 233, 0.1);
--danger-color: #ef4444;
--danger-light: rgba(239, 68, 68, 0.1);
--neutral-50: #f9fafb;
--neutral-100: #f3f4f6;
--neutral-200: #e5e7eb;
--neutral-300: #d1d5db;
--neutral-400: #9ca3af;
--neutral-500: #6b7280;
--neutral-600: #4b5563;
--neutral-700: #374151;
--neutral-800: #1f2937;
--neutral-900: #111827;
/* 样式变量 */
--border-radius-sm: 0.25rem;
--border-radius: 0.375rem;
--border-radius-lg: 0.5rem;
--border-radius-xl: 0.75rem;
--border-radius-2xl: 1rem;
--card-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);
--card-shadow-hover: 0 10px 20px rgba(0, 0, 0, 0.05), 0 6px 6px rgba(0, 0, 0, 0.1);
--sidebar-shadow: 0 0 20px rgba(0, 0, 0, 0.05);
--transition-base: all 0.2s ease-in-out;
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--font-family: 'Inter', 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif;
}
/* 基础样式 */
body {
font-family: var(--font-family);
background-color: var(--neutral-50);
color: var(--neutral-800);
display: flex;
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
color: var(--neutral-900);
}
.text-gradient {
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--neutral-100);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--neutral-300);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--neutral-400);
}
/* 侧边栏样式 */
.sidebar {
width: 260px;
background-color: white;
display: flex;
flex-direction: column;
box-shadow: var(--sidebar-shadow);
z-index: 1000;
position: relative;
overflow: hidden;
}
.sidebar::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 1px;
background: linear-gradient(to bottom,
var(--neutral-100) 0%,
var(--neutral-200) 50%,
var(--neutral-100) 100%);
}
.sidebar-logo {
display: flex;
align-items: center;
}
.sidebar-logo-icon {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
color: white;
border-radius: 12px;
font-size: 20px;
margin-right: 12px;
box-shadow: 0 4px 12px rgba(74, 108, 253, 0.3);
}
.sidebar-header {
padding: 1.75rem 1.5rem;
display: flex;
align-items: center;
border-bottom: 1px solid var(--neutral-100);
}
.sidebar-header h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.01em;
}
.sidebar-menu {
list-style: none;
padding: 1.25rem 0.75rem;
margin: 0;
flex: 1;
overflow-y: auto;
}
.sidebar-menu li {
margin-bottom: 0.5rem;
border-radius: var(--border-radius-lg);
transition: var(--transition-base);
}
.sidebar-menu li a {
display: flex;
align-items: center;
padding: 0.85rem 1rem;
color: var(--neutral-700);
text-decoration: none;
font-weight: 500;
border-radius: var(--border-radius-lg);
transition: var(--transition-base);
}
.sidebar-menu li:hover a {
color: var(--primary-color);
background-color: var(--neutral-100);
}
.sidebar-menu li.active {
background: linear-gradient(to right, var(--secondary-light), var(--secondary-color));
}
.sidebar-menu li.active a {
color: white;
font-weight: 600;
}
.sidebar-menu li i {
margin-right: 0.85rem;
font-size: 1.15rem;
opacity: 0.85;
}
/* 主内容区样式 */
.main-content {
flex: 1;
padding: 2rem 2.5rem;
overflow-y: auto;
background-color: var(--neutral-50);
}
/* 页面头部 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.content-header {
margin-bottom: 0.5rem;
}
.content-header h1 {
margin: 0;
font-size: 1.75rem;
font-weight: 700;
color: var(--primary-color);
letter-spacing: -0.01em;
}
.content-header p {
margin-top: 0.5rem;
color: var(--neutral-600);
font-size: 0.95rem;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* 卡片样式 */
.card {
background-color: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--card-shadow);
border: none;
transition: var(--transition-smooth);
overflow: hidden;
margin-bottom: 1.75rem;
}
.card:hover {
box-shadow: var(--card-shadow-hover);
}
.card-header {
background-color: white;
border-bottom: 1px solid var(--neutral-100);
padding: 1.25rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: var(--neutral-900);
display: flex;
align-items: center;
}
.card-header h3 i {
margin-right: 0.5rem;
font-size: 1.2rem;
color: var(--secondary-color);
opacity: 0.85;
}
.card-body {
padding: 1.5rem;
}
/* 统计卡片样式 */
.stat-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: var(--border-radius-xl);
padding: 1.5rem;
box-shadow: var(--card-shadow);
transition: var(--transition-smooth);
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: var(--card-shadow-hover);
}
.stat-card::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
border-radius: 3px 3px 0 0;
}
.stat-card.primary::after {
background: linear-gradient(to right, var(--secondary-color), var(--secondary-light));
}
.stat-card.success::after {
background: linear-gradient(to right, var(--success-color), #34d399);
}
.stat-card.warning::after {
background: linear-gradient(to right, var(--warning-color), #fbbf24);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 1.5rem;
flex-shrink: 0;
}
.stat-icon.primary {
background-color: var(--secondary-light);
color: white;
}
.stat-icon.success {
background-color: var(--success-color);
color: white;
}
.stat-icon.warning {
background-color: var(--warning-color);
color: white;
}
.stat-content {
flex: 1;
}
.stat-label {
font-size: 0.9rem;
color: var(--neutral-600);
margin: 0;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
color: var(--primary-color);
margin: 0.25rem 0 0 0;
line-height: 1;
}
/* Agent项目样式 */
.agent-item {
background: white;
border-radius: var(--border-radius-xl);
padding: 1.5rem;
margin-bottom: 1.25rem;
box-shadow: var(--card-shadow);
transition: var(--transition-smooth);
border: none;
position: relative;
overflow: hidden;
}
.agent-item::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 4px;
background: linear-gradient(to bottom, var(--secondary-color), var(--secondary-light));
border-radius: 20px;
}
.agent-item:hover {
transform: translateY(-5px);
box-shadow: var(--card-shadow-hover);
}
.agent-content {
padding-left: 0.5rem;
}
.agent-item .header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.agent-item .title {
font-size: 1.15rem;
font-weight: 600;
margin: 0;
color: var(--primary-color);
display: flex;
align-items: center;
}
.agent-item .title i {
margin-right: 0.5rem;
font-size: 1.1rem;
color: var(--secondary-color);
}
.agent-badges {
display: flex;
align-items: center;
gap: 0.5rem;
}
.agent-item .description {
color: var(--neutral-700);
margin-bottom: 1rem;
line-height: 1.5;
}
.agent-stats {
display: flex;
align-items: center;
gap: 1.5rem;
margin-top: 1.25rem;
padding-top: 1rem;
border-top: 1px solid var(--neutral-100);
}
.agent-stat {
display: flex;
flex-direction: column;
}
.agent-stat-value {
font-size: 1.1rem;
font-weight: 600;
color: var(--neutral-900);
}
.agent-stat-label {
font-size: 0.8rem;
color: var(--neutral-500);
margin-top: 0.25rem;
}
.agent-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
.agent-tag {
display: inline-flex;
align-items: center;
padding: 0.35rem 0.75rem;
background-color: var(--neutral-100);
border-radius: 20px;
font-size: 0.8rem;
color: var(--neutral-700);
}
.agent-tag i {
margin-right: 0.3rem;
font-size: 0.85rem;
}
.agent-actions {
margin-top: 1.25rem;
display: flex;
gap: 0.75rem;
}
/* 快速入门部分 */
.quick-start-item {
background-color: white;
border-radius: var(--border-radius-xl);
padding: 1.5rem;
margin-bottom: 1.25rem;
box-shadow: var(--card-shadow);
transition: var(--transition-smooth);
position: relative;
overflow: hidden;
}
.quick-start-item:hover {
transform: translateY(-5px);
box-shadow: var(--card-shadow-hover);
}
.quick-start-num {
position: absolute;
top: 1.25rem;
left: 1.25rem;
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
}
.quick-start-content {
padding-left: 3rem;
}
.quick-start-item h5 {
font-size: 1.05rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.quick-start-item p {
color: var(--neutral-600);
margin-bottom: 1rem;
font-size: 0.95rem;
}
/* 插件选项 */
.plugin-option {
background-color: white;
border-radius: var(--border-radius-xl);
padding: 1.25rem;
margin-bottom: 1rem;
box-shadow: var(--card-shadow);
transition: var(--transition-smooth);
display: flex;
align-items: center;
cursor: pointer;
border: 1px solid var(--neutral-100);
}
.plugin-option:hover {
transform: translateY(-3px);
box-shadow: var(--card-shadow-hover);
border-color: var(--neutral-200);
}
.plugin-option.selected {
border-color: var(--secondary-color);
background-color: rgba(74, 108, 253, 0.05);
}
.plugin-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background-color: var(--neutral-100);
color: var(--secondary-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-right: 1rem;
flex-shrink: 0;
}
.plugin-content {
flex: 1;
}
.plugin-title {
font-weight: 600;
margin-bottom: 0.25rem;
color: var(--neutral-900);
}
.plugin-description {
color: var(--neutral-600);
font-size: 0.9rem;
margin: 0;
}
/* 工作流画布 */
.workflow-canvas {
background-color: white;
border-radius: var(--border-radius-xl);
border: 1px solid var(--neutral-200);
min-height: 400px;
position: relative;
overflow: hidden;
}
.workflow-node {
position: absolute;
width: 180px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
user-select: none;
overflow: hidden;
transition: box-shadow 0.2s ease;
}
.workflow-node:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.node-header {
padding: 0.5rem;
display: flex;
align-items: center;
border-bottom: 1px solid var(--neutral-100);
}
.node-icon {
width: 24px;
height: 24px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.75rem;
margin-right: 0.5rem;
}
.node-title {
font-size: 0.85rem;
font-weight: 500;
color: var(--neutral-800);
}
.node-content {
padding: 0.75rem;
font-size: 0.8rem;
color: var(--neutral-600);
}
/* 节点类型 */
.node-type-intent_recognition .node-icon {
background-color: var(--secondary-color);
}
.node-type-knowledge_query .node-icon {
background-color: var(--success-color);
}
.node-type-plugin_call .node-icon {
background-color: var(--warning-color);
}
.node-type-generate_response .node-icon {
background-color: var(--info-color);
}
/* 知识库项目 */
.knowledge-item {
background-color: white;
border-radius: var(--border-radius-xl);
padding: 1.25rem;
margin-bottom: 1rem;
box-shadow: var(--card-shadow);
transition: var(--transition-smooth);
border: 1px solid var(--neutral-100);
}
.knowledge-item:hover {
transform: translateY(-3px);
box-shadow: var(--card-shadow-hover);
}
.knowledge-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.knowledge-title {
font-weight: 600;
margin: 0;
color: var(--neutral-900);
font-size: 1.05rem;
display: flex;
align-items: center;
}
.knowledge-title i {
margin-right: 0.5rem;
color: var(--secondary-color);
}
.knowledge-meta {
margin-top: 1rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.knowledge-meta-item {
display: flex;
align-items: center;
font-size: 0.85rem;
color: var(--neutral-600);
}
.knowledge-meta-item i {
margin-right: 0.4rem;
font-size: 0.9rem;
}
/* 按钮样式 */
.btn {
font-weight: 500;
padding: 0.65rem 1.25rem;
border-radius: var(--border-radius-lg);
transition: var(--transition-base);
box-shadow: none;
}
.btn-icon {
width: 36px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
}
.btn-xs {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
border-radius: 4px;
}
.btn-sm {
padding: 0.35rem 0.85rem;
font-size: 0.875rem;
}
.btn-primary {
background: linear-gradient(to right, var(--secondary-color), var(--secondary-light));
border: none;
}
.btn-primary:hover, .btn-primary:focus {
background: linear-gradient(to right, var(--secondary-light), var(--secondary-color));
box-shadow: 0 4px 10px rgba(74, 108, 253, 0.3);
transform: translateY(-1px);
}
.btn-outline-primary {
color: var(--secondary-color);
border-color: var(--secondary-color);
background-color: transparent;
}
.btn-outline-primary:hover, .btn-outline-primary:focus {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
color: white;
box-shadow: 0 4px 10px rgba(74, 108, 253, 0.2);
}
.btn-light {
background-color: var(--neutral-100);
color: var(--neutral-700);
border: none;
}
.btn-light:hover, .btn-light:focus {
background-color: var(--neutral-200);
color: var(--neutral-900);
}
.btn-success {
background-color: var(--success-color);
border-color: var(--success-color);
}
.btn-success:hover, .btn-success:focus {
background-color: var(--success-color);
border-color: var(--success-color);
opacity: 0.9;
box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3);
}
/* 徽章样式 */
.badge {
font-weight: 500;
padding: 0.35em 0.65em;
border-radius: 6px;
font-size: 0.75rem;
}
.badge-primary {
background-color: rgba(74, 108, 253, 0.15);
color: var(--secondary-color);
}
.badge-success {
background-color: rgba(16, 185, 129, 0.15);
color: var(--success-color);
}
.badge-warning {
background-color: rgba(245, 158, 11, 0.15);
color: var(--warning-color);
}
/* 表单样式 */
.form-control, .form-select {
padding: 0.65rem 1rem;
border-radius: var(--border-radius-lg);
border: 1px solid var(--neutral-200);
font-size: 0.95rem;
box-shadow: none;
transition: var(--transition-base);
}
.form-control:focus, .form-select:focus {
border-color: var(--secondary-color);
box-shadow: 0 0 0 3px rgba(74, 108, 253, 0.1);
}
.form-label {
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--neutral-800);
}
.input-group-text {
background-color: var(--neutral-100);
border-color: var(--neutral-200);
color: var(--neutral-700);
}
/* 标签页 */
.nav-tabs {
border-bottom: 1px solid var(--neutral-200);
margin-bottom: 1.5rem;
}
.nav-tabs .nav-link {
border: none;
color: var(--neutral-600);
padding: 0.75rem 1rem;
margin-right: 1rem;
font-weight: 500;
position: relative;
}
.nav-tabs .nav-link:after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background-color: transparent;
transition: var(--transition-base);
}
.nav-tabs .nav-link:hover {
color: var(--neutral-900);
border: none;
}
.nav-tabs .nav-link.active {
color: var(--secondary-color);
background-color: transparent;
border: none;
}
.nav-tabs .nav-link.active:after {
background-color: var(--secondary-color);
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
opacity: 0;
animation: fadeIn 0.4s forwards;
}
.animate-delay-1 { animation-delay: 0.1s; }
.animate-delay-2 { animation-delay: 0.2s; }
.animate-delay-3 { animation-delay: 0.3s; }
.animate-delay-4 { animation-delay: 0.4s; }
.animate-delay-5 { animation-delay: 0.5s; }
</style>
</head>
<body>
<!-- 侧边栏 -->
<div class="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<div class="sidebar-logo-icon">
<i class="bi bi-robot"></i>
</div>
<h2>AI助教开发平台</h2>
</div>
</div>
<ul class="sidebar-menu">
<li class="active">
<a href="#" data-section="dashboard">
<i class="bi bi-grid-1x2"></i>
<span>仪表盘</span>
</a>
</li>
<li>
<a href="#" data-section="create-agent">
<i class="bi bi-plus-circle"></i>
<span>创建 Agent</span>
</a>
</li>
<li>
<a href="#" data-section="knowledge-base">
<i class="bi bi-database"></i>
<span>知识库管理</span>
</a>
</li>
<li>
<a href="#" data-section="agent-list">
<i class="bi bi-list-ul"></i>
<span>Agent 列表</span>
</a>
</li>
<button class="btn btn-sm btn-outline-primary" id="logout-btn">登出</button>
</ul>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 仪表盘页面 -->
<section id="dashboard" class="content-section active">
<div class="page-header">
<div class="content-header">
<h1>仪表盘</h1>
<p>轻松创建和管理您的教育 AI 助手</p>
</div>
</div>
<!-- 统计卡片区域 -->
<div class="stat-container">
<div class="stat-card primary fade-in animate-delay-1">
<div class="stat-icon primary">
<i class="bi bi-robot"></i>
</div>
<div class="stat-content">
<p class="stat-label">AI 助手</p>
<h3 class="stat-value" id="agent-count">0</h3>
</div>
</div>
<div class="stat-card success fade-in animate-delay-2">
<div class="stat-icon success">
<i class="bi bi-database"></i>
</div>
<div class="stat-content">
<p class="stat-label">知识库</p>
<h3 class="stat-value" id="knowledge-count">0</h3>
</div>
</div>
<div class="stat-card warning fade-in animate-delay-3">
<div class="stat-icon warning">
<i class="bi bi-share"></i>
</div>
<div class="stat-content">
<p class="stat-label">分发链接</p>
<h3 class="stat-value" id="distribution-count">0</h3>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-7 fade-in animate-delay-4">
<div class="card">
<div class="card-header">
<h3><i class="bi bi-robot"></i> 最近创建的 Agent</h3>
<a href="#" class="text-decoration-none" style="color: var(--secondary-color);" onclick="switchSection('agent-list')">查看全部</a>
</div>
<div class="card-body" id="recent-agents">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-5 fade-in animate-delay-5">
<div class="card">
<div class="card-header">
<h3><i class="bi bi-lightning-charge"></i> 快速入门</h3>
</div>
<div class="card-body">
<div class="quick-start-item">
<div class="quick-start-num">1</div>
<div class="quick-start-content">
<h5>创建知识库</h5>
<p>上传教材、讲义等文档,构建 AI 助手的知识基础</p>
<button class="btn btn-sm btn-outline-primary" onclick="switchSection('knowledge-base')">
<i class="bi bi-plus me-1"></i> 开始创建
</button>
</div>
</div>
<div class="quick-start-item">
<div class="quick-start-num">2</div>
<div class="quick-start-content">
<h5>创建 Agent</h5>
<p>设计您的 AI 助手,选择插件和现有知识库</p>
<button class="btn btn-sm btn-outline-primary" onclick="switchSection('create-agent')">
<i class="bi bi-plus me-1"></i> 开始创建
</button>
</div>
</div>
<div class="quick-start-item">
<div class="quick-start-num">3</div>
<div class="quick-start-content">
<h5>分发给学生</h5>
<p>生成访问链接,分享给您的学生使用</p>
<button class="btn btn-sm btn-outline-primary" onclick="switchSection('agent-list')">
<i class="bi bi-share me-1"></i> 查看 Agent
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 创建Agent页面 -->
<section id="create-agent" class="content-section">
<div class="page-header">
<div class="content-header">
<h1>创建 Agent</h1>
<p>设计您的教育AI助手,定制其功能和知识源</p>
</div>
</div>
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs" id="agentCreationTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="basic-tab" data-bs-toggle="tab" data-bs-target="#basic" type="button" role="tab" aria-controls="basic" aria-selected="true">基本信息</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="plugins-tab" data-bs-toggle="tab" data-bs-target="#plugins" type="button" role="tab" aria-controls="plugins" aria-selected="false">插件选择</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="knowledge-tab" data-bs-toggle="tab" data-bs-target="#knowledge" type="button" role="tab" aria-controls="knowledge" aria-selected="false">知识库</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="workflow-tab" data-bs-toggle="tab" data-bs-target="#workflow" type="button" role="tab" aria-controls="workflow" aria-selected="false">工作流设计</button>
</li>
</ul>
<div class="tab-content" id="agentCreationTabContent">
<!-- 基本信息 -->
<div class="tab-pane fade show active" id="basic" role="tabpanel" aria-labelledby="basic-tab">
<div class="mt-4">
<div class="row">
<div class="col-md-8">
<div class="form-group mb-4">
<label for="agent-name" class="form-label">Agent 名称</label>
<input type="text" class="form-control" id="agent-name" placeholder="例如:计算机组成原理助教">
</div>
<div class="form-group mb-4">
<label for="agent-description" class="form-label">描述</label>
<textarea class="form-control" id="agent-description" rows="3" placeholder="描述这个Agent的功能和用途..."></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-4">
<label for="agent-subject" class="form-label">学科/课程名称</label>
<input type="text" class="form-control" id="agent-subject" placeholder="例如:计算机组成原理">
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-4">
<label for="agent-instructor" class="form-label">指导教师</label>
<input type="text" class="form-control" id="agent-instructor" placeholder="例如:李志刚教授">
</div>
</div>
</div>
<div class="form-group mb-4">
<label for="agent-type" class="form-label">Agent类型</label>
<select class="form-select" id="agent-type">
<option value="educational">教育辅导</option>
<option value="programming">编程辅助</option>
<option value="math">数学辅导</option>
<option value="general">通用助手</option>
</select>
</div>
</div>
</div>
<div class="d-flex justify-content-end mt-4">
<button class="btn btn-primary" onclick="nextTab('plugins-tab')">
下一步 <i class="bi bi-arrow-right ms-1"></i>
</button>
</div>
</div>
</div>
<!-- 插件选择 -->
<div class="tab-pane fade" id="plugins" role="tabpanel" aria-labelledby="plugins-tab">
<div class="mt-4">
<div class="mb-4">
<h5>选择Agent所需的插件</h5>
<p class="text-muted">插件可以扩展Agent的能力,使其更加强大</p>
</div>
<div class="plugin-option" data-plugin="code" onclick="togglePluginSelection(this)">
<div class="plugin-icon">
<i class="bi bi-code-square"></i>
</div>
<div class="plugin-content">
<div class="plugin-title">代码执行</div>
<div class="plugin-description">允许学生编写和执行Python代码,实时获取结果</div>
</div>
<div class="checkbox">
<input type="checkbox" class="form-check-input">
</div>
</div>
<div class="plugin-option" data-plugin="visualization" onclick="togglePluginSelection(this)">
<div class="plugin-icon">
<i class="bi bi-bar-chart"></i>
</div>
<div class="plugin-content">
<div class="plugin-title">3D可视化</div>
<div class="plugin-description">生成交互式3D图形,帮助学生理解数学概念</div>
</div>
<div class="checkbox">
<input type="checkbox" class="form-check-input">
</div>
</div>
<div class="plugin-option" data-plugin="mindmap" onclick="togglePluginSelection(this)">
<div class="plugin-icon">
<i class="bi bi-diagram-3"></i>
</div>
<div class="plugin-content">
<div class="plugin-title">思维导图</div>
<div class="plugin-description">创建结构化的思维导图,帮助学生梳理知识点</div>
</div>
<div class="checkbox">
<input type="checkbox" class="form-check-input">
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button class="btn btn-light" onclick="nextTab('basic-tab')">
<i class="bi bi-arrow-left me-1"></i> 上一步
</button>
<button class="btn btn-primary" onclick="nextTab('knowledge-tab')">
下一步 <i class="bi bi-arrow-right ms-1"></i>
</button>
</div>
</div>
</div>
<!-- 知识库 -->
<div class="tab-pane fade" id="knowledge" role="tabpanel" aria-labelledby="knowledge-tab">
<div class="mt-4">
<div class="mb-4">
<h5>选择Agent使用的知识库</h5>
<p class="text-muted">知识库提供Agent回答问题的专业知识</p>
</div>
<div class="mb-4">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" class="form-control" placeholder="搜索知识库..." id="knowledge-search">
</div>
</div>
<div id="knowledge-list" class="mb-4">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button class="btn btn-light" onclick="nextTab('plugins-tab')">
<i class="bi bi-arrow-left me-1"></i> 上一步
</button>
<button class="btn btn-primary" onclick="nextTab('workflow-tab')">
下一步 <i class="bi bi-arrow-right ms-1"></i>
</button>
</div>
</div>
</div>
<!-- 工作流设计 -->
<div class="tab-pane fade" id="workflow" role="tabpanel" aria-labelledby="workflow-tab">
<div class="mt-4">
<div class="mb-4">
<h5>设计Agent工作流</h5>
<p class="text-muted">定义Agent如何处理用户的请求</p>
</div>
<div class="mb-4">
<button class="btn btn-primary" id="ai-workflow-btn">
<i class="bi bi-magic"></i> 使用AI自动设计工作流
</button>
</div>
<div class="card mb-4">
<div class="card-body p-0">
<ul class="nav nav-tabs" id="workflowViewTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="visual-tab" data-bs-toggle="tab" data-bs-target="#visual-view" type="button" role="tab">可视化视图</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="code-tab" data-bs-toggle="tab" data-bs-target="#code-view" type="button" role="tab">代码视图</button>
</li>
</ul>
<div class="tab-content" id="workflowViewContent">
<div class="tab-pane fade show active" id="visual-view" role="tabpanel">
<div id="workflow-canvas" class="workflow-canvas">
<div class="p-4 text-center text-muted">
点击"使用AI自动设计工作流"按钮,或手动设计工作流
</div>
</div>
</div>
<div class="tab-pane fade" id="code-view" role="tabpanel">
<div class="p-4">
<pre id="workflow-code" class="mb-0 p-3 bg-light border rounded">{}</pre>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<button class="btn btn-light" onclick="nextTab('knowledge-tab')">
<i class="bi bi-arrow-left me-1"></i> 上一步
</button>
<button class="btn btn-success" id="create-agent-btn">
<i class="bi bi-check-lg me-1"></i> 创建 Agent
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 知识库管理 -->
<section id="knowledge-base" class="content-section">
<div class="page-header">
<div class="content-header">
<h1>知识库管理</h1>
<p>上传和管理Agent的知识源</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3><i class="bi bi-plus-circle"></i> 创建知识库</h3>
</div>
<div class="card-body">
<form id="knowledge-form">
<div class="form-group mb-3">
<label for="knowledge-name" class="form-label">知识库名称</label>
<input type="text" class="form-control" id="knowledge-name" placeholder="例如:计算机组成原理" required>
</div>
<div class="form-group mb-3">
<label for="knowledge-file" class="form-label">上传文件</label>
<input type="file" class="form-control" id="knowledge-file" required>
<div class="form-text">支持PDF、TXT、MD等格式</div>
</div>
<div class="progress mt-3 mb-2" style="display: none;" id="upload-progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
<button type="submit" class="btn btn-primary mt-3 w-100">
<i class="bi bi-upload me-1"></i> 创建知识库
</button>
</form>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3><i class="bi bi-collection"></i> 现有知识库</h3>
</div>
<div class="card-body">
<div id="existing-knowledge-list">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Agent列表 -->
<section id="agent-list" class="content-section">
<div class="page-header">
<div class="content-header">
<h1>Agent 列表</h1>
<p>管理和分发您创建的AI助手</p>
</div>
<div>
<button class="btn btn-primary" onclick="switchSection('create-agent')">
<i class="bi bi-plus-lg me-1"></i> 创建新Agent
</button>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div id="agent-list-container">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- 模态框:Agent详情 -->
<div class="modal fade" id="agentDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="agent-detail-title">Agent详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="agent-detail-body">
<!-- Agent详情将通过JavaScript填充 -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
</div>
<!-- 模态框:创建分发链接 -->
<div class="modal fade" id="createDistributionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">创建分发链接</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="distribution-form">
<input type="hidden" id="distribution-agent-id">
<div class="form-group mb-3">
<label for="distribution-expires" class="form-label">链接有效期</label>
<select class="form-select" id="distribution-expires">
<option value="0">永不过期</option>
<option value="86400">1天</option>
<option value="604800">7天</option>
<option value="2592000">30天</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="create-distribution-btn">创建链接</button>
</div>
</div>
</div>
</div>
<!-- 模态框:显示分发链接 -->
<div class="modal fade" id="showDistributionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">分发链接</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-success">
<i class="bi bi-check-circle me-2"></i> 链接创建成功!请复制以下链接分享给学生
</div>
<div class="input-group mb-3">
<input type="text" class="form-control" id="distribution-link" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyToClipboard('distribution-link')">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<script>
// 初始化变量
let selectedPlugins = [];
let selectedKnowledgeBases = [];
let currentWorkflow = { nodes: [], edges: [] };
let allKnowledgeBases = [];
let allAgents = [];
let jsPlumbWorkflowInstance = null; // 添加jsPlumb实例变量
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 初始化侧边栏菜单点击事件
document.querySelectorAll('.sidebar-menu li a').forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
switchSection(section);
});
});
// 初始化表单提交事件
document.getElementById('knowledge-form').addEventListener('submit', createKnowledgeBase);
// 初始化AI工作流按钮
document.getElementById('ai-workflow-btn').addEventListener('click', generateAIWorkflow);
// 初始化创建Agent按钮
document.getElementById('create-agent-btn').addEventListener('click', createAgent);
// 初始化创建分发链接按钮
document.getElementById('create-distribution-btn').addEventListener('click', createDistribution);
// 初始化工作流视图标签切换
initWorkflowTabs();
// 加载知识库列表
loadKnowledgeBases();
// 加载Agent列表
loadAgents();
// 初始化登出按钮
document.getElementById('logout-btn').addEventListener('click', logout);
});
// 登出功能
async function logout() {
try {
const response = await fetch('/api/auth/logout', {
method: 'POST'
});
const data = await response.json();
if (data.success) {
window.location.href = '/login.html';
} else {
alert('登出失败: ' + data.message);
}
} catch (error) {
console.error('登出出错:', error);
alert('登出失败,请重试');
}
}
// 初始化工作流标签切换
function initWorkflowTabs() {
// 为主工作流和详情工作流标签添加点击事件
document.querySelectorAll('#workflowViewTabs .nav-link, #detailWorkflowTabs .nav-link').forEach(tab => {
tab.addEventListener('click', function(event) {
event.preventDefault();
// 获取目标面板ID
const targetId = this.getAttribute('data-bs-target');
const targetPane = document.querySelector(targetId);
if (!targetPane) return;
// 获取所有同级标签并移除激活状态
const tabs = this.closest('.nav-tabs').querySelectorAll('.nav-link');
tabs.forEach(t => t.classList.remove('active'));
// 激活当前标签
this.classList.add('active');
// 获取所有同级面板并隐藏
const tabContent = targetPane.closest('.tab-content');
if (tabContent) {
tabContent.querySelectorAll('.tab-pane').forEach(pane => {
pane.classList.remove('show', 'active');
});
}
// 显示目标面板
targetPane.classList.add('show', 'active');
// 如果切换到可视化视图,重新渲染工作流
if (targetId === '#visual-view' || targetId === '#detail-visual-view') {
const canvasId = targetId === '#visual-view' ? 'workflow-canvas' : 'detail-workflow-canvas';
// 确定要使用的工作流数据
let workflowData = currentWorkflow;
if (targetId === '#detail-visual-view') {
const modalBody = document.getElementById('agent-detail-body');
if (modalBody && modalBody.dataset.agentWorkflow) {
try {
workflowData = JSON.parse(modalBody.dataset.agentWorkflow);
} catch (e) {
console.error('解析工作流数据出错:', e);
}
}
}
// 延迟一小段时间确保DOM已经更新
setTimeout(() => {
renderWorkflowVisualization(workflowData, canvasId);
}, 200);
}
});
});
}
// 切换内容区域
function switchSection(section) {
// 隐藏所有内容区域
document.querySelectorAll('.content-section').forEach(item => {
item.classList.remove('active');
});
// 显示选中的内容区域
document.getElementById(section).classList.add('active');
// 更新侧边栏选中状态
document.querySelectorAll('.sidebar-menu li').forEach(item => {
item.classList.remove('active');
if (item.querySelector('a').getAttribute('data-section') === section) {
item.classList.add('active');
}
});
// 如果切换到了工作流视图,重新绘制连接
if (section === 'create-agent') {
// 获取当前激活的标签页
const activeTab = document.querySelector('#agentCreationTabs .nav-link.active');
if (activeTab && activeTab.id === 'workflow-tab') {
// 检查当前哪个视图处于激活状态
const activeView = document.querySelector('#workflowViewContent .tab-pane.active');
if (activeView && activeView.id === 'visual-view') {
// 延迟执行以确保DOM已更新
setTimeout(() => {
renderWorkflowVisualization(currentWorkflow, 'workflow-canvas');
}, 200);
}
}
}
}
// 切换到下一个选项卡
function nextTab(tabId) {
const tab = document.getElementById(tabId);
const bsTab = new bootstrap.Tab(tab);
bsTab.show();
}
// 切换插件选择状态
function togglePluginSelection(element) {
const pluginId = element.getAttribute('data-plugin');
const checkbox = element.querySelector('input[type="checkbox"]');
if (element.classList.contains('selected')) {
element.classList.remove('selected');
checkbox.checked = false;
selectedPlugins = selectedPlugins.filter(id => id !== pluginId);
} else {
element.classList.add('selected');
checkbox.checked = true;
selectedPlugins.push(pluginId);
}
}
// 切换知识库选择状态
function toggleKnowledgeSelection(element) {
const knowledgeId = element.getAttribute('data-id');
if (element.classList.contains('selected')) {
element.classList.remove('selected');
element.querySelector('input[type="checkbox"]').checked = false;
selectedKnowledgeBases = selectedKnowledgeBases.filter(id => id !== knowledgeId);
} else {
element.classList.add('selected');
element.querySelector('input[type="checkbox"]').checked = true;
selectedKnowledgeBases.push(knowledgeId);
}
}
// 加载知识库列表
async function loadKnowledgeBases() {
try {
const response = await fetch('/api/knowledge');
const result = await response.json();
if (result.success) {
allKnowledgeBases = result.data || [];
// 更新仪表盘计数
document.getElementById('knowledge-count').textContent = allKnowledgeBases.length;
// 更新Agent创建页面的知识库列表
const knowledgeListElement = document.getElementById('knowledge-list');
if (knowledgeListElement) {
if (allKnowledgeBases.length === 0) {
knowledgeListElement.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
暂无知识库,请先创建知识库
</div>
<div class="text-center">
<button class="btn btn-primary" onclick="switchSection('knowledge-base')">
创建知识库
</button>
</div>
`;
} else {
knowledgeListElement.innerHTML = '';
allKnowledgeBases.forEach(knowledge => {
const item = document.createElement('div');
item.className = 'plugin-option';
item.setAttribute('data-id', knowledge.id);
item.setAttribute('onclick', 'toggleKnowledgeSelection(this)');
item.innerHTML = `
<div class="plugin-icon">
<i class="bi bi-journal-text"></i>
</div>
<div class="plugin-content">
<div class="plugin-title">${knowledge.name}</div>
<div class="plugin-description">
<small>${knowledge.fileCount || 0}个文件</small>
</div>
</div>
<div class="checkbox">
<input type="checkbox" class="form-check-input">
</div>
`;
knowledgeListElement.appendChild(item);
});
}
}
// 更新知识库管理页面的列表
const existingKnowledgeList = document.getElementById('existing-knowledge-list');
if (existingKnowledgeList) {
if (allKnowledgeBases.length === 0) {
existingKnowledgeList.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
暂无知识库,请创建第一个知识库
</div>
`;
} else {
existingKnowledgeList.innerHTML = '';
allKnowledgeBases.forEach(knowledge => {
const item = document.createElement('div');
item.className = 'knowledge-item';
let fileListHtml = '';
if (knowledge.files && knowledge.files.length > 0) {
fileListHtml = '<div class="knowledge-files mt-3">';
knowledge.files.forEach(file => {
// 使用文件名来判断文件类型
const isImage = /\.(jpg|jpeg|png|gif|bmp)$/i.test(file);
const isPdf = /\.pdf$/i.test(file);
const isDoc = /\.(doc|docx)$/i.test(file);
const isTxt = /\.txt$/i.test(file);
const isMd = /\.md$/i.test(file);
let iconClass = 'bi-file-text';
if (isImage) iconClass = 'bi-file-image';
if (isPdf) iconClass = 'bi-file-pdf';
if (isDoc) iconClass = 'bi-file-earmark-word';
if (isTxt) iconClass = 'bi-file-text';
if (isMd) iconClass = 'bi-file-text';
fileListHtml += `
<div class="d-flex align-items-center p-2 border-bottom">
<i class="bi ${iconClass} me-2" style="font-size: 1.2rem; color: var(--secondary-color);"></i>
<div class="flex-grow-1 text-truncate" title="${file}">
${file}
</div>
<button class="btn btn-sm btn-icon btn-light" onclick="deleteFile('${knowledge.id}', '${encodeURIComponent(file)}')">
<i class="bi bi-trash"></i>
</button>
</div>
`;
});
fileListHtml += '</div>';
}
item.innerHTML = `
<div class="knowledge-header">
<h5 class="knowledge-title">
<i class="bi bi-journal-text"></i> ${knowledge.name}
</h5>
<div>
<button class="btn btn-sm btn-light me-1" onclick="editKnowledgeBase('${knowledge.id}')">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger" onclick="deleteKnowledgeBase('${knowledge.id}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
${fileListHtml}
<div class="knowledge-meta">
<div class="knowledge-meta-item">
<i class="bi bi-file-text"></i>
<span>${knowledge.fileCount || 0}个文件</span>
</div>
<div class="knowledge-meta-item">
<i class="bi bi-calendar3"></i>
<span>创建于 ${new Date(knowledge.created_at * 1000).toLocaleDateString()}</span>
</div>
</div>
<div class="mt-3">
<form class="upload-form" data-id="${knowledge.id}">
<div class="input-group">
<input type="file" class="form-control form-control-sm" required>
<button type="submit" class="btn btn-sm btn-outline-primary">添加文件</button>
</div>
</form>
</div>
`;
existingKnowledgeList.appendChild(item);
});
// 添加上传表单事件监听
document.querySelectorAll('.upload-form').forEach(form => {
form.addEventListener('submit', function(event) {
event.preventDefault();
addFileToKnowledgeBase(form);
});
});
}
}
} else {
console.error('加载知识库失败:', result.message);
}
} catch (error) {
console.error('加载知识库出错:', error);
}
}
// 创建知识库
async function createKnowledgeBase(event) {
event.preventDefault();
const nameInput = document.getElementById('knowledge-name');
const fileInput = document.getElementById('knowledge-file');
const progressBar = document.getElementById('upload-progress');
const submitBtn = event.submitter || document.querySelector('#knowledge-form button[type="submit"]');
if (!nameInput.value || !fileInput.files[0]) {
alert('请填写知识库名称并选择文件');
return;
}
// 获取文件扩展名
const fileName = fileInput.files[0].name;
const fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
// 检查文件类型
const supportedExts = ['.txt', '.pdf', '.md', '.jpg', '.jpeg', '.png', '.gif', '.bmp'];
if (!supportedExts.includes(fileExt)) {
alert(`不支持的文件类型: ${fileExt}。\n支持的文件类型: ${supportedExts.join(', ')}`);
return;
}
// 检查文件大小
const fileSize = fileInput.files[0].size;
const fileSizeMB = fileSize / (1024 * 1024);
const maxFileSizeMB = 16; // 最大16MB
if (fileSizeMB > maxFileSizeMB) {
alert(`文件大小(${fileSizeMB.toFixed(2)}MB)超过最大限制(${maxFileSizeMB}MB)`);
return;
}
// 显示进度条并禁用提交按钮
progressBar.style.display = 'block';
progressBar.querySelector('.progress-bar').style.width = '0%';
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 上传中...';
const formData = new FormData();
formData.append('name', nameInput.value);
formData.append('file', fileInput.files[0]);
try {
// 使用 fetch 的 timeout 设置
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
const response = await fetch('/api/knowledge', {
method: 'POST',
body: formData,
signal: controller.signal
});
clearTimeout(timeoutId);
const result = await response.json();
if (result.success) {
// 成功启动处理,轮询进度
pollProgress(result.task_id, progressBar, submitBtn);
} else {
alert('创建知识库失败: ' + result.message);
progressBar.style.display = 'none';
submitBtn.disabled = false;
submitBtn.textContent = '创建知识库';
}
} catch (error) {
console.error('创建知识库出错:', error);
// 判断是否是超时错误
let errorMessage = '创建知识库出错,请查看控制台日志';
if (error.name === 'AbortError') {
errorMessage = '上传请求超时,请检查网络连接或尝试上传较小的文件';
}
alert(errorMessage);
progressBar.style.display = 'none';
submitBtn.disabled = false;
submitBtn.textContent = '创建知识库';
}
}
// 添加文件到知识库
async function addFileToKnowledgeBase(form) {
const knowledgeId = form.getAttribute('data-id');
const fileInput = form.querySelector('input[type="file"]');
const submitBtn = form.querySelector('button[type="submit"]');
if (!fileInput.files[0]) {
alert('请选择文件');
return;
}
// 获取文件扩展名
const fileName = fileInput.files[0].name;
const fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
// 检查文件类型
const supportedExts = ['.txt', '.pdf', '.md', '.jpg', '.jpeg', '.png', '.gif', '.bmp'];
if (!supportedExts.includes(fileExt)) {
alert(`不支持的文件类型: ${fileExt}。\n支持的文件类型: ${supportedExts.join(', ')}`);
return;
}
// 检查文件大小
const fileSize = fileInput.files[0].size;
const fileSizeMB = fileSize / (1024 * 1024);
const maxFileSizeMB = 16; // 最大16MB
if (fileSizeMB > maxFileSizeMB) {
alert(`文件大小(${fileSizeMB.toFixed(2)}MB)超过最大限制(${maxFileSizeMB}MB)`);
return;
}
// 禁用提交按钮
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 上传中...';
const formData = new FormData();
formData.append('file', fileInput.files[0]);
try {
// 使用 fetch 的 timeout 设置
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
const response = await fetch(`/api/knowledge/${knowledgeId}/documents`, {
method: 'POST',
body: formData,
signal: controller.signal
});
clearTimeout(timeoutId);
const result = await response.json();
if (result.success) {
alert('文件上传成功,开始处理');
// 轮询处理进度
pollProgressSimple(result.task_id, submitBtn);
} else {
alert('添加文件失败: ' + result.message);
submitBtn.disabled = false;
submitBtn.textContent = '添加文件';
}
} catch (error) {
console.error('添加文件出错:', error);
// 判断是否是超时错误
let errorMessage = '添加文件出错,请查看控制台日志';
if (error.name === 'AbortError') {
errorMessage = '上传请求超时,请检查网络连接或尝试上传较小的文件';
}
alert(errorMessage);
submitBtn.disabled = false;
submitBtn.textContent = '添加文件';
}
}
// 删除知识库
async function deleteKnowledgeBase(knowledgeId) {
if (!confirm('确定要删除这个知识库吗?此操作不可恢复。')) {
return;
}
try {
const response = await fetch(`/api/knowledge/${knowledgeId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('知识库删除成功!');
loadKnowledgeBases(); // 重新加载知识库列表
} else {
alert('删除知识库失败: ' + result.message);
}
} catch (error) {
console.error('删除知识库出错:', error);
alert('删除知识库出错,请查看控制台日志');
}
}
// 删除知识库中的单个文件
async function deleteFile(indexId, encodedFileName) {
try {
// 解码文件名以在确认对话框中显示
const fileName = decodeURIComponent(encodedFileName);
if (!confirm(`确定要删除文件 "${fileName}" 吗?此操作不可恢复。`)) {
return;
}
const response = await fetch(`/api/knowledge/${indexId}/documents/${encodedFileName}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('文件删除成功!');
loadKnowledgeBases(); // 重新加载知识库列表
} else {
alert('删除文件失败: ' + result.message);
}
} catch (error) {
console.error('删除文件出错:', error);
alert('删除文件出错,请查看控制台日志');
}
}
// 轮询进度(带进度条显示)
function pollProgress(taskId, progressElement, submitBtn) {
const progressBar = progressElement.querySelector('.progress-bar');
let retryCount = 0;
const maxRetries = 3;
const interval = setInterval(async () => {
try {
const response = await fetch(`/api/progress/${taskId}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
if (data.success) {
const progressData = data.data;
// 更新进度条
progressBar.style.width = progressData.progress + '%';
progressBar.setAttribute('aria-valuenow', progressData.progress);
// 更新进度状态
if (submitBtn.tagName === 'BUTTON') {
submitBtn.innerHTML = `<i class="bi bi-arrow-repeat spin me-1"></i> ${progressData.status || '处理中...'}`;
}
// 处理完成或出错时
if (progressData.progress >= 100 || progressData.error) {
clearInterval(interval);
if (progressData.error) {
alert('处理失败: ' + progressData.status);
} else {
alert(`知识库处理成功! 已处理${progressData.docCount}个文档片段`);
}
// 隐藏进度条
progressElement.style.display = 'none';
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.innerHTML = '创建知识库';
// 重置表单
document.getElementById('knowledge-name').value = '';
document.getElementById('knowledge-file').value = '';
// 重新加载知识库列表
loadKnowledgeBases();
}
} else {
throw new Error('轮询返回错误状态');
}
// 重置重试计数
retryCount = 0;
} catch (error) {
console.error('获取进度信息出错:', error);
retryCount++;
if (retryCount >= maxRetries) {
clearInterval(interval);
alert('获取进度信息失败,请检查控制台日志');
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.innerHTML = '创建知识库';
progressElement.style.display = 'none';
}
}
}, 1000); // 每秒轮询一次
}
// 简化版轮询进度(不显示进度条)
function pollProgressSimple(taskId, buttonElement) {
const originalText = buttonElement.textContent;
let retryCount = 0;
const maxRetries = 3;
buttonElement.innerHTML = '<i class="bi bi-arrow-repeat spin me-1"></i> 处理中...';
const interval = setInterval(async () => {
try {
const response = await fetch(`/api/progress/${taskId}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
if (data.success) {
const progressData = data.data;
// 更新按钮文本显示进度
buttonElement.innerHTML = `<i class="bi bi-arrow-repeat spin me-1"></i> ${progressData.status || '处理中...'} (${progressData.progress}%)`;
// 处理完成或出错时
if (progressData.progress >= 100 || progressData.error) {
clearInterval(interval);
if (progressData.error) {
alert('处理失败: ' + progressData.status);
} else {
alert(`处理成功! 已处理${progressData.docCount}个文档片段`);
}
// 恢复按钮
buttonElement.disabled = false;
buttonElement.textContent = originalText;
// 重新加载知识库列表
loadKnowledgeBases();
}
} else {
throw new Error('轮询返回错误状态');
}
// 重置重试计数
retryCount = 0;
} catch (error) {
console.error('获取进度信息出错:', error);
retryCount++;
if (retryCount >= maxRetries) {
clearInterval(interval);
alert('获取进度信息失败,请检查控制台日志');
// 恢复按钮
buttonElement.disabled = false;
buttonElement.textContent = originalText;
}
}
}, 1000); // 每秒轮询一次
}
// 根据文件扩展名获取图标类
function getFileIconClass(fileName) {
if (!fileName) return 'bi-file';
const ext = fileName.split('.').pop().toLowerCase();
switch (ext) {
case 'pdf': return 'bi-file-pdf';
case 'doc':
case 'docx': return 'bi-file-earmark-word';
case 'xls':
case 'xlsx': return 'bi-file-earmark-excel';
case 'ppt':
case 'pptx': return 'bi-file-earmark-ppt';
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'bmp': return 'bi-file-image';
case 'md': return 'bi-markdown';
case 'txt': return 'bi-file-text';
default: return 'bi-file';
}
}
// 使用AI生成工作流
async function generateAIWorkflow() {
const description = document.getElementById('agent-description').value;
const subject = document.getElementById('agent-subject').value || document.getElementById('agent-name').value;
if (!description) {
alert('请先填写Agent描述,以便AI生成工作流');
nextTab('basic-tab');
return;
}
// 显示加载状态
const workflowCanvas = document.getElementById('workflow-canvas');
workflowCanvas.innerHTML = `
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">生成中...</span>
</div>
<p class="mt-3">AI正在设计工作流,请稍候...</p>
</div>
`;
try {
const response = await fetch('/api/agent/ai-assist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
description: description,
subject: subject,
plugins: selectedPlugins,
knowledge_bases: selectedKnowledgeBases
})
});
const result = await response.json();
if (result.success) {
// 保存工作流
currentWorkflow = result.workflow;
// 更新代码视图
updateWorkflowCodeView(currentWorkflow);
// 处理AI推荐的知识库和插件
let recommendationHtml = '';
let recommendationsApplied = false;
// 应用推荐的知识库
if (result.recommended_knowledge_bases && result.recommended_knowledge_bases.length > 0) {
// 更新选择的知识库
selectedKnowledgeBases = result.recommended_knowledge_bases;
// 更新UI中的选择状态
document.querySelectorAll('#knowledge-list .plugin-option').forEach(item => {
const kbId = item.getAttribute('data-id');
const selected = selectedKnowledgeBases.includes(kbId);
item.classList.toggle('selected', selected);
const checkbox = item.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = selected;
});
recommendationsApplied = true;
recommendationHtml += `
<div class="mb-3">
<h6>AI推荐的知识库:</h6>
<ul>
${selectedKnowledgeBases.map(kb => `<li>${kb}</li>`).join('')}
</ul>
</div>
`;
}
// 应用推荐的插件
if (result.recommended_plugins && result.recommended_plugins.length > 0) {
// 更新选择的插件
selectedPlugins = result.recommended_plugins;
// 更新UI中的选择状态
document.querySelectorAll('[data-plugin]').forEach(item => {
const pluginId = item.getAttribute('data-plugin');
const selected = selectedPlugins.includes(pluginId);
item.classList.toggle('selected', selected);
const checkbox = item.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = selected;
});
recommendationsApplied = true;
recommendationHtml += `
<div class="mb-3">
<h6>AI推荐的插件:</h6>
<ul>
${selectedPlugins.map(plugin => {
let pluginName = plugin;
if (plugin === 'code') pluginName = '代码执行';
if (plugin === 'visualization') pluginName = '3D可视化';
if (plugin === 'mindmap') pluginName = '思维导图';
return `<li>${pluginName}</li>`;
}).join('')}
</ul>
</div>
`;
}
// 显示推荐结果
if (recommendationsApplied) {
workflowCanvas.innerHTML = `
<div class="alert alert-success mb-3">
<i class="bi bi-check-circle-fill me-2"></i>
AI已成功设计工作流并推荐了知识库和插件
</div>
${recommendationHtml}
<div id="workflow-visualization-container"></div>
`;
} else {
workflowCanvas.innerHTML = `
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle-fill me-2"></i>
AI已设计工作流但未推荐额外的知识库或插件
</div>
<div id="workflow-visualization-container"></div>
`;
}
// 渲染工作流视图
setTimeout(() => {
renderWorkflowVisualization(currentWorkflow, 'workflow-canvas');
}, 200);
} else {
workflowCanvas.innerHTML = `
<div class="alert alert-danger m-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
生成工作流失败: ${result.message}
</div>
`;
}
} catch (error) {
console.error('生成工作流出错:', error);
workflowCanvas.innerHTML = `
<div class="alert alert-danger m-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
生成工作流时发生错误,请重试
</div>
`;
}
}
// 更新工作流代码视图
function updateWorkflowCodeView(workflow) {
const workflowCodeElement = document.getElementById('workflow-code');
if (workflowCodeElement) {
workflowCodeElement.textContent = JSON.stringify(workflow, null, 2);
}
}
// 加载Agent列表
async function loadAgents() {
try {
const response = await fetch('/api/agent/list');
const result = await response.json();
if (result.success) {
allAgents = result.agents || [];
// 更新仪表盘计数
document.getElementById('agent-count').textContent = allAgents.length;
// 计算分发链接总数
let distributionCount = 0;
allAgents.forEach(agent => {
distributionCount += agent.distribution_count || 0;
});
document.getElementById('distribution-count').textContent = distributionCount;
// 更新最近创建的Agent
updateRecentAgents();
// 更新Agent列表页面
updateAgentListPage();
} else {
console.error('加载Agent列表失败:', result.message);
}
} catch (error) {
console.error('加载Agent列表出错:', error);
}
}
// 更新最近创建的Agent列表
function updateRecentAgents() {
const recentAgentsElement = document.getElementById('recent-agents');
if (recentAgentsElement) {
if (allAgents.length === 0) {
recentAgentsElement.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
您还没有创建Agent
</div>
<div class="text-center">
<button class="btn btn-primary" onclick="switchSection('create-agent')">
创建第一个Agent
</button>
</div>
`;
} else {
// 只显示最近3个Agent
const recentAgents = allAgents.slice(0, 3);
recentAgentsElement.innerHTML = '';
recentAgents.forEach(agent => {
const item = document.createElement('div');
item.className = 'agent-item';
const createdDate = new Date(agent.created_at * 1000);
const formattedDate = createdDate.toLocaleDateString();
const typeText = getAgentTypeText(agent.type);
// 构建标签
let agentTagsHtml = '';
if (agent.subject || agent.instructor) {
agentTagsHtml = '<div class="agent-tags">';
if (agent.subject) {
agentTagsHtml += `<span class="agent-tag"><i class="bi bi-book"></i> ${agent.subject}</span>`;
}
if (agent.instructor) {
agentTagsHtml += `<span class="agent-tag"><i class="bi bi-person"></i> ${agent.instructor}</span>`;
}
agentTagsHtml += '</div>';
}
item.innerHTML = `
<div class="agent-content">
<div class="header">
<h5 class="title"><i class="bi bi-robot"></i> ${agent.name}</h5>
<span class="badge badge-primary">${typeText}</span>
</div>
<div class="description">${agent.description || '暂无描述'}</div>
${agentTagsHtml}
<div class="agent-stats">
<div class="agent-stat">
<span class="agent-stat-value">${formattedDate}</span>
<span class="agent-stat-label">创建时间</span>
</div>
<div class="agent-stat">
<span class="agent-stat-value">${agent.distribution_count || 0}</span>
<span class="agent-stat-label">分发链接</span>
</div>
<div class="agent-stat">
<span class="agent-stat-value">${agent.usage_count || 0}</span>
<span class="agent-stat-label">使用次数</span>
</div>
</div>
</div>
`;
recentAgentsElement.appendChild(item);
});
}
}
}
// 更新Agent列表页面
function updateAgentListPage() {
const agentListContainer = document.getElementById('agent-list-container');
if (agentListContainer) {
if (allAgents.length === 0) {
agentListContainer.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
您还没有创建Agent
</div>
<div class="text-center">
<button class="btn btn-primary" onclick="switchSection('create-agent')">
创建第一个Agent
</button>
</div>
`;
} else {
agentListContainer.innerHTML = '';
allAgents.forEach(agent => {
const item = document.createElement('div');
item.className = 'agent-item';
const createdDate = new Date(agent.created_at * 1000);
const formattedDate = createdDate.toLocaleDateString();
const typeText = getAgentTypeText(agent.type);
// 构建插件和知识库标签
let tagsHtml = '<div class="agent-tags">';
if (agent.subject) {
tagsHtml += `<span class="agent-tag"><i class="bi bi-book"></i> ${agent.subject}</span>`;
}
if (agent.instructor) {
tagsHtml += `<span class="agent-tag"><i class="bi bi-person"></i> ${agent.instructor}</span>`;
}
if (agent.plugins && agent.plugins.length > 0) {
agent.plugins.forEach(plugin => {
let pluginName = '未知';
let pluginIcon = 'puzzle';
if (plugin === 'code') {
pluginName = '代码执行';
pluginIcon = 'code-square';
} else if (plugin === 'visualization') {
pluginName = '3D可视化';
pluginIcon = 'bar-chart';
} else if (plugin === 'mindmap') {
pluginName = '思维导图';
pluginIcon = 'diagram-3';
}
tagsHtml += `
<span class="agent-tag">
<i class="bi bi-${pluginIcon}"></i> ${pluginName}
</span>
`;
});}
tagsHtml += '</div>';
item.innerHTML = `
<div class="agent-content">
<div class="header">
<h5 class="title"><i class="bi bi-robot"></i> ${agent.name}</h5>
<span class="badge badge-primary">${typeText}</span>
</div>
<div class="description">${agent.description || '暂无描述'}</div>
${tagsHtml}
<div class="agent-stats">
<div class="agent-stat">
<span class="agent-stat-value">${formattedDate}</span>
<span class="agent-stat-label">创建时间</span>
</div>
<div class="agent-stat">
<span class="agent-stat-value">${agent.distribution_count || 0}</span>
<span class="agent-stat-label">分发链接</span>
</div>
<div class="agent-stat">
<span class="agent-stat-value">${agent.usage_count || 0}</span>
<span class="agent-stat-label">使用次数</span>
</div>
</div>
<div class="agent-actions">
<button class="btn btn-sm btn-primary" onclick="showCreateDistributionModal('${agent.id}')">
<i class="bi bi-share me-1"></i> 分发
</button>
<button class="btn btn-sm btn-light" onclick="showAgentDetail('${agent.id}')">
<i class="bi bi-info-circle me-1"></i> 详情
</button>
<button class="btn btn-sm btn-light" onclick="editAgent('${agent.id}')">
<i class="bi bi-pencil me-1"></i> 编辑
</button>
<button class="btn btn-sm btn-light text-danger" onclick="deleteAgent('${agent.id}')">
<i class="bi bi-trash me-1"></i> 删除
</button>
</div>
</div>
`;
agentListContainer.appendChild(item);
});
}
}
}
// 获取Agent类型文本
function getAgentTypeText(type) {
switch (type) {
case 'educational': return '教育辅导';
case 'programming': return '编程辅助';
case 'math': return '数学辅导';
case 'general': return '通用助手';
default: return type || '教育辅导';
}
}
// 显示Agent详情
async function showAgentDetail(agentId) {
try {
// 获取Agent详细信息
const response = await fetch(`/api/agent/${agentId}`);
const result = await response.json();
if (result.success) {
const agent = result.agent;
// 更新模态框内容
const modalTitle = document.getElementById('agent-detail-title');
const modalBody = document.getElementById('agent-detail-body');
modalTitle.textContent = agent.name;
// 格式化创建时间
const createdDate = new Date(agent.created_at * 1000);
const formattedDate = createdDate.toLocaleString();
const typeText = getAgentTypeText(agent.type);
// 计算总使用次数(累加所有分发链接的使用次数)
let totalUsageCount = 0;
if (agent.distributions && agent.distributions.length > 0) {
agent.distributions.forEach(dist => {
totalUsageCount += (dist.usage_count || 0);
});
}
// 构建模态框内容
modalBody.innerHTML = `
<div class="card mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-info-circle me-2"></i>基本信息</h6>
</div>
<div class="card-body">
<div class="mb-3">
<h6 class="text-muted mb-2">描述</h6>
<p class="mb-0">${agent.description || '暂无描述'}</p>
</div>
<div class="row mb-3">
<div class="col-md-6">
<h6 class="text-muted mb-2">学科/课程</h6>
<p class="mb-0">${agent.subject || '-'}</p>
</div>
<div class="col-md-6">
<h6 class="text-muted mb-2">指导教师</h6>
<p class="mb-0">${agent.instructor || '-'}</p>
</div>
</div>
<div class="row mb-0">
<div class="col-md-4">
<h6 class="text-muted mb-2">类型</h6>
<p class="mb-0">${typeText}</p>
</div>
<div class="col-md-4">
<h6 class="text-muted mb-2">创建时间</h6>
<p class="mb-0">${formattedDate}</p>
</div>
<div class="col-md-4">
<h6 class="text-muted mb-2">总使用次数</h6>
<p class="mb-0"><span class="badge bg-primary">${totalUsageCount}</span></p>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-puzzle me-2"></i>插件</h6>
</div>
<div class="card-body">
${getPluginsHtml(agent.plugins)}
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-journal-text me-2"></i>知识库</h6>
</div>
<div class="card-body">
${getKnowledgeBasesHtml(agent.knowledge_bases)}
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-share me-2"></i>分发链接</h6>
</div>
<div class="card-body">
${getDistributionsHtml(agent.distributions, agent.id)}
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-diagram-3 me-2"></i>工作流配置</h6>
</div>
<div class="card-body">
<ul class="nav nav-tabs mb-3" id="detailWorkflowTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="detail-visual-tab" data-bs-toggle="tab" data-bs-target="#detail-visual-view" type="button" role="tab">可视化视图</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="detail-code-tab" data-bs-toggle="tab" data-bs-target="#detail-code-view" type="button" role="tab">代码视图</button>
</li>
</ul>
<div class="tab-content" id="detailWorkflowContent">
<div class="tab-pane fade show active" id="detail-visual-view" role="tabpanel">
<div id="detail-workflow-canvas" class="workflow-canvas"></div>
</div>
<div class="tab-pane fade" id="detail-code-view" role="tabpanel">
<div class="p-3 bg-light border rounded">
<pre class="mb-0">${JSON.stringify(agent.workflow, null, 2)}</pre>
</div>
</div>
</div>
</div>
</div>
`;
// 存储工作流数据
modalBody.dataset.agentWorkflow = JSON.stringify(agent.workflow);
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('agentDetailModal'));
modal.show();
// 在模态框显示后初始化工作流标签事件
document.getElementById('agentDetailModal').addEventListener('shown.bs.modal', function() {
// 立即渲染工作流可视化
setTimeout(() => {
const workflow = JSON.parse(modalBody.dataset.agentWorkflow);
renderWorkflowVisualization(workflow, 'detail-workflow-canvas');
}, 200);
});
} else {
alert('获取Agent详情失败: ' + result.message);
}
} catch (error) {
console.error('获取Agent详情出错:', error);
alert('获取Agent详情出错,请查看控制台日志');
}
}
// 辅助函数,用于生成分发链接HTML
function getDistributionsHtml(distributions, agentId) {
if (!distributions || distributions.length === 0) {
return `
<div class="text-center py-3">
<p class="text-muted">暂无分发链接</p>
<button class="btn btn-sm btn-primary" onclick="showCreateDistributionModal('${agentId}')">
<i class="bi bi-plus-lg me-1"></i> 创建分发链接
</button>
</div>
`;
}
let html = '<div class="mb-3">';
distributions.forEach(dist => {
const expiryText = dist.expires_at > 0
? `过期时间: ${new Date(dist.expires_at * 1000).toLocaleString()}`
: '永不过期';
// 生成访问令牌和完整URL
const accessToken = `${agentId}?token=${dist.token}`;
const fullUrl = `${window.location.origin}/student/${accessToken}`;
html += `
<div class="p-3 mb-3 border rounded bg-light">
<div class="mb-3">
<h6 class="text-muted mb-2">完整链接</h6>
<div class="input-group">
<input type="text" class="form-control" value="${fullUrl}" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyToClipboard('${fullUrl}')">
<i class="bi bi-clipboard"></i> 复制
</button>
</div>
</div>
<div class="mb-3">
<h6 class="text-muted mb-2">学生免登录访问令牌</h6>
<div class="input-group">
<input type="text" class="form-control" value="${accessToken}" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyToClipboard('${accessToken}')">
<i class="bi bi-clipboard"></i> 复制
</button>
</div>
</div>
<div class="d-flex justify-content-between">
<div class="small text-muted">
<span class="me-3">${expiryText}</span>
</div>
<div class="small text-muted">
<span>链接使用次数: ${dist.usage_count || 0}</span>
</div>
</div>
</div>
`;
});
html += `
<div class="text-center mt-3">
<button class="btn btn-sm btn-primary" onclick="showCreateDistributionModal('${agentId}')">
<i class="bi bi-plus-lg me-1"></i> 创建新链接
</button>
</div>
</div>`;
return html;
}
// 辅助函数,用于生成插件HTML
function getPluginsHtml(plugins) {
if (!plugins || plugins.length === 0) {
return '<p>无</p>';
}
let html = '<div class="agent-tags">';
plugins.forEach(plugin => {
let pluginName = '未知';
let pluginIcon = 'puzzle';
if (plugin === 'code') {
pluginName = '代码执行';
pluginIcon = 'code-square';
} else if (plugin === 'visualization') {
pluginName = '3D可视化';
pluginIcon = 'bar-chart';
} else if (plugin === 'mindmap') {
pluginName = '思维导图';
pluginIcon = 'diagram-3';
}
html += `<span class="agent-tag"><i class="bi bi-${pluginIcon}"></i> ${pluginName}</span>`;
});
html += '</div>';
return html;
}
// 辅助函数,用于生成知识库HTML
function getKnowledgeBasesHtml(knowledgeBases) {
if (!knowledgeBases || knowledgeBases.length === 0) {
return '<p>无</p>';
}
let html = '<div class="agent-tags">';
knowledgeBases.forEach(kb => {
// 查找知识库名称
const knowledge = allKnowledgeBases.find(k => k.id === kb);
const knowledgeName = knowledge ? knowledge.name : kb;
html += `<span class="agent-tag"><i class="bi bi-journal-text"></i> ${knowledgeName}</span>`;
});
html += '</div>';
return html;
}
// 创建Agent
async function createAgent() {
const name = document.getElementById('agent-name').value;
const description = document.getElementById('agent-description').value;
const subject = document.getElementById('agent-subject').value || name; // 使用课程名称,如果未填写则使用Agent名称
const instructor = document.getElementById('agent-instructor').value || '教师'; // 使用指导教师,如果未填写则使用默认值
const type = document.getElementById('agent-type').value;
if (!name) {
alert('请填写Agent名称');
nextTab('basic-tab');
return;
}
// 准备Agent数据
const agentData = {
name: name,
description: description,
subject: subject,
instructor: instructor,
type: type,
plugins: selectedPlugins,
knowledge_bases: selectedKnowledgeBases,
workflow: currentWorkflow
};
try {
const response = await fetch('/api/agent/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(agentData)
});
const result = await response.json();
if (result.success) {
alert(`Agent '${name}' 创建成功!`);
// 重置表单
document.getElementById('agent-name').value = '';
document.getElementById('agent-description').value = '';
document.getElementById('agent-subject').value = '';
document.getElementById('agent-instructor').value = '';
document.getElementById('agent-type').value = 'educational';
selectedPlugins = [];
selectedKnowledgeBases = [];
currentWorkflow = { nodes: [], edges: [] };
// 重置插件选择
document.querySelectorAll('.plugin-option').forEach(item => {
item.classList.remove('selected');
const checkbox = item.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = false;
});
// 重置工作流视图
document.getElementById('workflow-canvas').innerHTML = `
<div class="p-4 text-center text-muted">
点击"使用AI自动设计工作流"按钮,或手动设计工作流
</div>
`;
// 更新代码视图
updateWorkflowCodeView(currentWorkflow);
// 切换到Agent列表页面
switchSection('agent-list');
// 重新加载Agent列表
loadAgents();
} else {
alert('创建Agent失败: ' + result.message);
}
} catch (error) {
console.error('创建Agent出错:', error);
alert('创建Agent出错,请查看控制台日志');
}
}
// 编辑Agent
function editAgent(agentId) {
const agent = allAgents.find(a => a.id === agentId);
if (!agent) {
alert('找不到要编辑的Agent');
return;
}
// 填充表单
document.getElementById('agent-name').value = agent.name || '';
document.getElementById('agent-description').value = agent.description || '';
document.getElementById('agent-subject').value = agent.subject || '';
document.getElementById('agent-instructor').value = agent.instructor || '';
document.getElementById('agent-type').value = agent.type || 'educational';
// 设置选择的插件
selectedPlugins = agent.plugins || [];
document.querySelectorAll('[data-plugin]').forEach(item => {
const pluginId = item.getAttribute('data-plugin');
const selected = selectedPlugins.includes(pluginId);
item.classList.toggle('selected', selected);
const checkbox = item.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = selected;
});
// 设置选择的知识库
selectedKnowledgeBases = agent.knowledge_bases || [];
document.querySelectorAll('#knowledge-list .plugin-option').forEach(item => {
const kbId = item.getAttribute('data-id');
const selected = selectedKnowledgeBases.includes(kbId);
item.classList.toggle('selected', selected);
const checkbox = item.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = selected;
});
// 设置工作流
currentWorkflow = agent.workflow || { nodes: [], edges: [] };
updateWorkflowCodeView(currentWorkflow);
// 切换到创建Agent页面和基本信息标签
switchSection('create-agent');
nextTab('basic-tab');
// TODO: 添加表单提交处理,区分创建和更新
}
// 删除Agent
async function deleteAgent(agentId) {
if (!confirm('确定要删除这个Agent吗?此操作不可恢复。')) {
return;
}
try {
const response = await fetch(`/api/agent/${agentId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('Agent删除成功!');
loadAgents(); // 重新加载Agent列表
} else {
alert('删除Agent失败: ' + result.message);
}
} catch (error) {
console.error('删除Agent出错:', error);
alert('删除Agent出错,请查看控制台日志');
}
}
// 显示创建分发链接模态框
function showCreateDistributionModal(agentId) {
document.getElementById('distribution-agent-id').value = agentId;
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('createDistributionModal'));
modal.show();
}
// 创建分发链接
async function createDistribution() {
const agentId = document.getElementById('distribution-agent-id').value;
const expiresIn = parseInt(document.getElementById('distribution-expires').value);
if (!agentId) {
alert('Agent ID不能为空');
return;
}
try {
const response = await fetch(`/api/agent/${agentId}/distribute`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ expires_in: expiresIn })
});
const result = await response.json();
if (result.success) {
// 隐藏创建模态框
const createModal = bootstrap.Modal.getInstance(document.getElementById('createDistributionModal'));
createModal.hide();
// 生成访问令牌和完整URL
const accessToken = `${agentId}?token=${result.distribution.token}`;
const fullUrl = `${window.location.origin}/student/${accessToken}`;
// 更新分发链接输入框
document.getElementById('showDistributionModal').querySelector('.modal-body').innerHTML = `
<div class="alert alert-success">
<i class="bi bi-check-circle me-2"></i> 链接创建成功!请复制以下链接分享给学生
</div>
<div class="mb-3">
<h6 class="text-muted mb-2">完整链接</h6>
<div class="input-group">
<input type="text" class="form-control" id="full-distribution-link" value="${fullUrl}" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyToClipboard('full-distribution-link')">
<i class="bi bi-clipboard"></i> 复制
</button>
</div>
</div>
<div class="mb-3">
<h6 class="text-muted mb-2">学生免登录访问令牌</h6>
<div class="input-group">
<input type="text" class="form-control" id="access-token" value="${accessToken}" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyToClipboard('access-token')">
<i class="bi bi-clipboard"></i> 复制
</button>
</div>
</div>
`;
// 显示分发链接模态框
const showModal = new bootstrap.Modal(document.getElementById('showDistributionModal'));
showModal.show();
// 重新加载Agent列表
loadAgents();
} else {
alert('创建分发链接失败: ' + result.message);
}
} catch (error) {
console.error('创建分发链接出错:', error);
alert('创建分发链接出错,请查看控制台日志');
}
}
// 复制到剪贴板
function copyToClipboard(textOrElementId) {
let text;
if (textOrElementId.startsWith('/') || textOrElementId.startsWith('http')) {
// 直接传入的文本
text = textOrElementId;
} else {
// 传入的是元素ID
const element = document.getElementById(textOrElementId);
text = element.value || element.textContent;
}
navigator.clipboard.writeText(text).then(() => {
alert('已复制到剪贴板!');
}).catch(err => {
console.error('复制失败:', err);
});
}
// 根据节点类型获取图标
function getNodeIcon(type) {
switch (type) {
case 'intent_recognition': return 'AI';
case 'knowledge_query': return 'KB';
case 'plugin_call': return 'API';
case 'generate_response': return 'AI';
default: return '';
}
}
// 渲染工作流可视化
function renderWorkflowVisualization(workflow, containerId) {
const container = document.getElementById(containerId);
if (!container) return;
// 清空容器
container.innerHTML = '';
// 如果没有工作流数据,显示提示
if (!workflow || !workflow.nodes || workflow.nodes.length === 0) {
container.innerHTML = `
<div class="p-4 text-center text-muted">
<i class="bi bi-diagram-3 fs-2 mb-3"></i>
<p>暂无工作流数据</p>
</div>
`;
return;
}
// 创建或重置jsPlumb实例
if (jsPlumbWorkflowInstance) {
jsPlumbWorkflowInstance.reset();
}
jsPlumbWorkflowInstance = jsPlumb.getInstance({
Endpoint: ["Dot", { radius: 4 }],
Connector: ["Bezier", { curviness: 50 }],
PaintStyle: { stroke: "#4a6cfd", strokeWidth: 2 },
HoverPaintStyle: { stroke: "#7b91ff", strokeWidth: 3 },
ConnectionOverlays: [
["Arrow", { location: 1, width: 10, length: 10, foldback: 0.7 }]
],
Container: containerId
});
// 创建节点
workflow.nodes.forEach(node => {
const nodeEl = document.createElement('div');
nodeEl.id = node.id;
nodeEl.className = `workflow-node node-type-${node.type}`;
nodeEl.innerHTML = `
<div class="node-header">
<div class="node-icon">${getNodeIcon(node.type)}</div>
<div class="node-title">${node.data.name}</div>
</div>
<div class="node-content">${node.data.description || ''}</div>
`;
container.appendChild(nodeEl);
});
// 计算节点依赖关系
const dependencies = {};
const dependents = {};
workflow.nodes.forEach(node => {
dependencies[node.id] = [];
dependents[node.id] = [];
});
workflow.edges.forEach(edge => {
dependencies[edge.target].push(edge.source);
dependents[edge.source].push(edge.target);
});
// 计算层级
const nodeLevels = {};
const queue = [];
// 找到没有依赖的节点(入度为0的节点)
workflow.nodes.forEach(node => {
if (dependencies[node.id].length === 0) {
nodeLevels[node.id] = 0;
queue.push(node.id);
}
});
// BFS计算层级
while (queue.length > 0) {
const currentId = queue.shift();
const currentLevel = nodeLevels[currentId];
dependents[currentId].forEach(dependentId => {
const allDependenciesProcessed = dependencies[dependentId].every(
depId => nodeLevels[depId] !== undefined
);
if (allDependenciesProcessed) {
const maxDependencyLevel = Math.max(
...dependencies[dependentId].map(depId => nodeLevels[depId])
);
nodeLevels[dependentId] = maxDependencyLevel + 1;
queue.push(dependentId);
}
});
}
// 按层级对节点进行布局
const levelGroups = {};
Object.keys(nodeLevels).forEach(nodeId => {
const level = nodeLevels[nodeId];
if (!levelGroups[level]) {
levelGroups[level] = [];
}
levelGroups[level].push(nodeId);
});
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const levelCount = Object.keys(levelGroups).length;
const levelHeight = containerHeight / (levelCount > 1 ? levelCount : 2);
Object.entries(levelGroups).forEach(([level, nodeIds]) => {
const levelIndex = parseInt(level);
const nodeCount = nodeIds.length;
const nodeWidth = containerWidth / (nodeCount + 1);
nodeIds.forEach((nodeId, index) => {
const node = document.getElementById(nodeId);
if (node) {
node.style.top = `${levelIndex * levelHeight + 50}px`;
node.style.left = `${(index + 1) * nodeWidth - (node.clientWidth / 2)}px`;
}
});
});
// 为每个节点创建可拖动功能
workflow.nodes.forEach(node => {
const nodeEl = document.getElementById(node.id);
if (nodeEl) {
jsPlumbWorkflowInstance.draggable(nodeEl, {
containment: container
});
}
});
// 添加连接
setTimeout(() => {
workflow.edges.forEach(edge => {
const sourceEl = document.getElementById(edge.source);
const targetEl = document.getElementById(edge.target);
if (sourceEl && targetEl) {
const connection = jsPlumbWorkflowInstance.connect({
source: sourceEl,
target: targetEl,
anchors: ["Bottom", "Top"],
connector: ["Bezier", { curviness: 50 }],
paintStyle: { stroke: "#4a6cfd", strokeWidth: 2 },
endpoint: "Blank",
overlays: [
["Arrow", { location: 1, width: 10, length: 10, foldback: 0.7 }]
]
});
// 添加连接标签
if (edge.condition) {
connection.addOverlay([
"Label", {
label: edge.condition,
cssClass: "connection-label",
location: 0.5
}
]);
}
}
});
// 刷新所有连接
jsPlumbWorkflowInstance.repaintEverything();
}, 100);
}
</script>
</body>
</html>