| |
| <!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/bootstrap@5.2.3/dist/css/bootstrap.min.css"> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/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-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> |
| </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> |
| |
| |
| <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> |
| |
| |
| <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> |
| |
| |
| <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"> |
| |
| </div> |
| <div class="modal-footer"> |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> |
| </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; |
| |
| |
| 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); |
| |
| |
| document.getElementById('ai-workflow-btn').addEventListener('click', generateAIWorkflow); |
| |
| |
| document.getElementById('create-agent-btn').addEventListener('click', createAgent); |
| |
| |
| document.getElementById('create-distribution-btn').addEventListener('click', createDistribution); |
| |
| |
| initWorkflowTabs(); |
| |
| |
| loadKnowledgeBases(); |
| |
| |
| loadAgents(); |
| }); |
| |
| |
| function initWorkflowTabs() { |
| |
| document.querySelectorAll('#workflowViewTabs .nav-link, #detailWorkflowTabs .nav-link').forEach(tab => { |
| tab.addEventListener('click', function(event) { |
| event.preventDefault(); |
| |
| |
| 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); |
| } |
| } |
| } |
| |
| |
| 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') { |
| |
| 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; |
| |
| |
| 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">'; |
| knowledge.files.forEach(file => { |
| const isImage = file.toLowerCase().endsWith('.jpg') || file.toLowerCase().endsWith('.png') || file.toLowerCase().endsWith('.jpeg'); |
| const isPdf = file.toLowerCase().endsWith('.pdf'); |
| const isDoc = file.toLowerCase().endsWith('.doc') || file.toLowerCase().endsWith('.docx'); |
| |
| let iconClass = 'bi-file-text'; |
| if (isImage) iconClass = 'bi-file-image'; |
| if (isPdf) iconClass = 'bi-file-pdf'; |
| if (isDoc) iconClass = 'bi-file-earmark-word'; |
| |
| fileListHtml += ` |
| <div class="knowledge-file"> |
| <div class="knowledge-file-icon"> |
| <i class="bi ${iconClass}"></i> |
| </div> |
| <div class="knowledge-file-info"> |
| <div class="knowledge-file-name">${file}</div> |
| </div> |
| <button class="btn btn-sm btn-icon btn-light" onclick="downloadFile('${knowledge.id}', '${file}')"> |
| <i class="bi bi-download"></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'); |
| |
| if (!nameInput.value || !fileInput.files[0]) { |
| alert('请填写知识库名称并选择文件'); |
| return; |
| } |
| |
| |
| progressBar.style.display = 'block'; |
| progressBar.querySelector('.progress-bar').style.width = '0%'; |
| |
| const formData = new FormData(); |
| formData.append('name', nameInput.value); |
| formData.append('file', fileInput.files[0]); |
| |
| try { |
| const response = await fetch('/api/knowledge', { |
| method: 'POST', |
| body: formData |
| }); |
| |
| const result = await response.json(); |
| |
| if (result.success) { |
| |
| pollProgress(result.task_id, progressBar); |
| } else { |
| alert('创建知识库失败: ' + result.message); |
| progressBar.style.display = 'none'; |
| } |
| } catch (error) { |
| console.error('创建知识库出错:', error); |
| alert('创建知识库出错,请查看控制台日志'); |
| progressBar.style.display = 'none'; |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| submitBtn.disabled = true; |
| submitBtn.textContent = '上传中...'; |
| |
| const formData = new FormData(); |
| formData.append('file', fileInput.files[0]); |
| |
| try { |
| const response = await fetch(`/api/knowledge/${knowledgeId}/documents`, { |
| method: 'POST', |
| body: formData |
| }); |
| |
| 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); |
| alert('添加文件出错,请查看控制台日志'); |
| 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('删除知识库出错,请查看控制台日志'); |
| } |
| } |
| |
| |
| function pollProgress(taskId, progressElement) { |
| const progressBar = progressElement.querySelector('.progress-bar'); |
| |
| const interval = setInterval(async () => { |
| try { |
| const response = await fetch(`/api/progress/${taskId}`); |
| const data = await response.json(); |
| |
| if (data.success) { |
| const progressData = data.data; |
| |
| |
| progressBar.style.width = progressData.progress + '%'; |
| |
| |
| if (progressData.progress >= 100 || progressData.error) { |
| clearInterval(interval); |
| |
| if (progressData.error) { |
| alert('处理失败: ' + progressData.status); |
| } else { |
| alert(`知识库处理成功! 已处理${progressData.docCount}个文档片段`); |
| } |
| |
| |
| progressElement.style.display = 'none'; |
| |
| |
| document.getElementById('knowledge-name').value = ''; |
| document.getElementById('knowledge-file').value = ''; |
| |
| |
| loadKnowledgeBases(); |
| } |
| } |
| } catch (error) { |
| console.error('获取进度信息出错:', error); |
| } |
| }, 1000); |
| } |
| |
| |
| function pollProgressSimple(taskId, buttonElement) { |
| const originalText = buttonElement.textContent; |
| |
| const interval = setInterval(async () => { |
| try { |
| const response = await fetch(`/api/progress/${taskId}`); |
| const data = await response.json(); |
| |
| if (data.success) { |
| const progressData = data.data; |
| |
| |
| 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(); |
| } |
| } |
| } catch (error) { |
| console.error('获取进度信息出错:', error); |
| } |
| }, 1000); |
| } |
| |
| |
| 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); |
| |
| |
| let recommendationHtml = ''; |
| let recommendationsApplied = false; |
| |
| |
| if (result.recommended_knowledge_bases && result.recommended_knowledge_bases.length > 0) { |
| |
| selectedKnowledgeBases = result.recommended_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; |
| }); |
| |
| 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; |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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; |
| |
| |
| updateRecentAgents(); |
| |
| |
| updateAgentListPage(); |
| } else { |
| console.error('加载Agent列表失败:', result.message); |
| } |
| } catch (error) { |
| console.error('加载Agent列表出错:', error); |
| } |
| } |
| |
| |
| 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 { |
| |
| 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); |
| }); |
| } |
| } |
| } |
| |
| |
| 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); |
| }); |
| } |
| } |
| } |
| |
| |
| function getAgentTypeText(type) { |
| switch (type) { |
| case 'educational': return '教育辅导'; |
| case 'programming': return '编程辅助'; |
| case 'math': return '数学辅导'; |
| case 'general': return '通用助手'; |
| default: return type || '教育辅导'; |
| } |
| } |
| |
| |
| async function showAgentDetail(agentId) { |
| try { |
| |
| 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); |
| |
| |
| modalBody.innerHTML = ` |
| <div class="mb-3"> |
| <h6>描述</h6> |
| <p>${agent.description || '暂无描述'}</p> |
| </div> |
| |
| <div class="row mb-3"> |
| <div class="col-md-6"> |
| <h6>学科/课程</h6> |
| <p>${agent.subject || '-'}</p> |
| </div> |
| <div class="col-md-6"> |
| <h6>指导教师</h6> |
| <p>${agent.instructor || '-'}</p> |
| </div> |
| </div> |
| |
| <div class="row mb-3"> |
| <div class="col-md-6"> |
| <h6>类型</h6> |
| <p>${typeText}</p> |
| </div> |
| <div class="col-md-6"> |
| <h6>创建时间</h6> |
| <p>${formattedDate}</p> |
| </div> |
| </div> |
| |
| <div class="mb-3"> |
| <h6>插件</h6> |
| ${getPluginsHtml(agent.plugins)} |
| </div> |
| |
| <div class="mb-3"> |
| <h6>知识库</h6> |
| ${getKnowledgeBasesHtml(agent.knowledge_bases)} |
| </div> |
| |
| <div class="mb-3"> |
| <h6>分发链接</h6> |
| ${getDistributionsHtml(agent.distributions, agent.id)} |
| </div> |
| |
| <div class="mb-4"> |
| <h6>工作流配置</h6> |
| <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> |
| `; |
| |
| |
| 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详情出错,请查看控制台日志'); |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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()}` |
| : '永不过期'; |
| |
| const url = `/student/${agentId}?token=${dist.token}`; |
| |
| html += ` |
| <div class="p-3 mb-2 border rounded"> |
| <div class="d-flex justify-content-between align-items-center mb-2"> |
| <div class="fw-medium">${url}</div> |
| <button class="btn btn-sm btn-outline-primary" onclick="copyToClipboard('${url}')"> |
| <i class="bi bi-clipboard"></i> 复制 |
| </button> |
| </div> |
| <div class="small text-muted"> |
| <span class="me-3">${expiryText}</span> |
| <span>使用次数: ${dist.usage_count || 0}</span> |
| </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; |
| } |
| |
| |
| 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; |
| const instructor = document.getElementById('agent-instructor').value || '教师'; |
| const type = document.getElementById('agent-type').value; |
| |
| if (!name) { |
| alert('请填写Agent名称'); |
| nextTab('basic-tab'); |
| return; |
| } |
| |
| |
| 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); |
| |
| |
| switchSection('agent-list'); |
| |
| |
| loadAgents(); |
| } else { |
| alert('创建Agent失败: ' + result.message); |
| } |
| } catch (error) { |
| console.error('创建Agent出错:', error); |
| alert('创建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); |
| |
| |
| switchSection('create-agent'); |
| nextTab('basic-tab'); |
| |
| |
| } |
| |
| |
| 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(); |
| } 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(); |
| |
| |
| const fullUrl = window.location.origin + result.distribution.link; |
| document.getElementById('distribution-link').value = fullUrl; |
| |
| |
| const showModal = new bootstrap.Modal(document.getElementById('showDistributionModal')); |
| showModal.show(); |
| |
| |
| 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 { |
| |
| 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; |
| } |
| |
| |
| 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 = []; |
| |
| |
| workflow.nodes.forEach(node => { |
| if (dependencies[node.id].length === 0) { |
| nodeLevels[node.id] = 0; |
| queue.push(node.id); |
| } |
| }); |
| |
| |
| 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> |