2026-01-12 14:20:44 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<title>大模型微调平台 - 主页</title>
|
|
|
|
|
|
<!-- Tailwind CSS CDN -->
|
|
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
|
<!-- Chart.js CDN -->
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
|
|
|
|
|
<!-- 自定义Tailwind配置(匹配浅色主题) -->
|
2026-01-12 14:20:44 +08:00
|
|
|
|
<script>
|
|
|
|
|
|
tailwind.config = {
|
|
|
|
|
|
theme: {
|
|
|
|
|
|
extend: {
|
|
|
|
|
|
colors: {
|
2026-01-13 12:09:41 +08:00
|
|
|
|
dashboard: {
|
|
|
|
|
|
bg: '#DBE0F9', // 主背景色(与login一致)
|
|
|
|
|
|
card: '#FFFFFF', // 卡片背景
|
|
|
|
|
|
primary: '#6065D9', // 主蓝色
|
|
|
|
|
|
secondary: '#764BA2', // 深蓝紫色
|
|
|
|
|
|
accent: '#17D7FA', // 浅蓝高亮
|
|
|
|
|
|
text: '#333333', // 文字色
|
|
|
|
|
|
textLight: '#666666' // 浅文字色
|
|
|
|
|
|
}
|
2026-01-12 14:20:44 +08:00
|
|
|
|
},
|
2026-01-13 12:09:41 +08:00
|
|
|
|
fontFamily: {
|
|
|
|
|
|
inter: ['Inter', 'sans-serif'],
|
|
|
|
|
|
},
|
|
|
|
|
|
animation: {
|
|
|
|
|
|
'pulse-slow': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2026-01-12 14:20:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<!-- 网格布局样式 -->
|
|
|
|
|
|
<style>
|
|
|
|
|
|
/* 2x2网格布局 - 通用 */
|
|
|
|
|
|
.grid-2x2 {
|
|
|
|
|
|
display: grid !important;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(2, auto) !important;
|
|
|
|
|
|
gap: 1rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 1x4网格布局 - 用于模型选择区域 */
|
|
|
|
|
|
.grid-1x4 {
|
|
|
|
|
|
display: grid !important;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: 1 !important;
|
|
|
|
|
|
gap: 1rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 结果展示区域的2x2布局 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 {
|
|
|
|
|
|
display: grid !important;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(2, auto) !important;
|
|
|
|
|
|
gap: 1rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 模型选择区域的1x4布局 */
|
|
|
|
|
|
#compare-config-section .grid-1x4 {
|
|
|
|
|
|
display: grid !important;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: 1 !important;
|
|
|
|
|
|
gap: 1rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 确保结果展示的卡片是网格项 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 确保模型输出卡片正确排列 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(1) { grid-row: 1; grid-column: 1; } /* 模型A - 左上 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(2) { grid-row: 1; grid-column: 2; } /* 模型B - 右上 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(3) { grid-row: 2; grid-column: 1; } /* 模型C - 左下 */
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(4) { grid-row: 2; grid-column: 2; } /* 模型D - 右下 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 响应式调整 */
|
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
|
#compare-config-section .grid-1x4 {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(2, auto) !important;
|
|
|
|
|
|
gap: 0.75rem !important;
|
2026-01-12 14:20:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-13 12:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
#comparisonResults .grid-2x2 {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(2, auto) !important;
|
|
|
|
|
|
gap: 0.75rem !important;
|
|
|
|
|
|
}
|
2026-01-12 14:20:44 +08:00
|
|
|
|
}
|
2026-01-13 12:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
|
|
|
|
#compare-config-section .grid-1x4 {
|
|
|
|
|
|
grid-template-columns: repeat(1, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(4, auto) !important;
|
|
|
|
|
|
gap: 0.75rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
#comparisonResults .grid-2x2 {
|
|
|
|
|
|
grid-template-columns: repeat(1, 1fr) !important;
|
|
|
|
|
|
grid-template-rows: repeat(4, auto) !important;
|
|
|
|
|
|
gap: 0.75rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(1) { grid-row: 1; grid-column: 1; }
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(2) { grid-row: 2; grid-column: 1; }
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(3) { grid-row: 3; grid-column: 1; }
|
|
|
|
|
|
#comparisonResults .grid-2x2 > div:nth-child(4) { grid-row: 4; grid-column: 1; }
|
2026-01-12 14:20:44 +08:00
|
|
|
|
}
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
<style type="text/tailwindcss">
|
|
|
|
|
|
@layer utilities {
|
|
|
|
|
|
.gradient-blue {
|
|
|
|
|
|
background: linear-gradient(135deg, #6065D9 0%, #17D7FA 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-shadow {
|
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
.progress-bar {
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.radial-progress {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
height: 120px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.radial-progress::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
width: 90%;
|
|
|
|
|
|
height: 90%;
|
|
|
|
|
|
background-color: #FFFFFF;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
}
|
2026-01-12 14:20:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
|
|
<!-- 登录验证脚本 -->
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 页面加载时检查登录状态
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
|
|
|
|
|
|
|
|
|
|
|
|
if (!isLoggedIn) {
|
|
|
|
|
|
// 如果未登录,重定向到登录页面
|
|
|
|
|
|
window.location.href = 'login.html';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</head>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<body class="bg-dashboard-bg text-dashboard-text font-inter min-h-screen">
|
|
|
|
|
|
<div class="flex">
|
|
|
|
|
|
<!-- 侧边栏 -->
|
|
|
|
|
|
<aside class="w-16 md:w-64 bg-white h-screen p-4 flex flex-col shadow-lg">
|
|
|
|
|
|
<div class="flex items-center mb-8">
|
|
|
|
|
|
<div class="w-10 h-10 rounded-lg gradient-blue flex items-center justify-center">
|
|
|
|
|
|
<span class="text-white font-bold">AI</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="ml-2 text-lg font-semibold hidden md:block text-dashboard-primary">大模型平台</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<nav class="flex-1">
|
|
|
|
|
|
<ul class="space-y-4">
|
|
|
|
|
|
<li><a href="#" data-page="dashboard" class="nav-item flex items-center p-2 rounded-lg gradient-blue text-white">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">仪表盘</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="preprocess" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">数据预处理</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="pretrain" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">模型预训练</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="finetune" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">模型微调</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="rl" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">强化训练</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="validate" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">模型验证</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="compare" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">模型对比</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="dataset" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">数据集管理</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" data-page="config" class="nav-item flex items-center p-2 rounded-lg hover:bg-dashboard-primary/10 text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">模型配置</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
<li><a href="#" onclick="logout()" class="flex items-center p-2 rounded-lg hover:bg-red-100 text-dashboard-textLight hover:text-red-600">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path></svg>
|
|
|
|
|
|
<span class="hidden md:block">退出登录</span>
|
|
|
|
|
|
</a></li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区 -->
|
|
|
|
|
|
<main class="flex-1" style="height: calc(100vh - 0px);">
|
|
|
|
|
|
<!-- 仪表盘页面 -->
|
|
|
|
|
|
<div id="dashboard-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<!-- 顶部数据卡片 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 card-shadow">
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">活跃模型</p>
|
|
|
|
|
|
<p class="text-2xl font-bold mt-1 text-dashboard-text">12</p>
|
|
|
|
|
|
<div class="flex items-center mt-2 text-green-500 text-sm">
|
|
|
|
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path></svg>
|
|
|
|
|
|
<span>+3个</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 card-shadow">
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">训练中项目</p>
|
|
|
|
|
|
<p class="text-2xl font-bold mt-1 text-dashboard-text">5</p>
|
|
|
|
|
|
<div class="flex items-center mt-2 text-blue-500 text-sm">
|
|
|
|
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
|
|
|
|
<span>运行中</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 card-shadow">
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">完成任务</p>
|
|
|
|
|
|
<p class="text-2xl font-bold mt-1 text-dashboard-text">158</p>
|
|
|
|
|
|
<div class="flex items-center mt-2 text-green-500 text-sm">
|
|
|
|
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
|
|
|
|
<span>全部成功</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 中部核心卡片 - 系统监控 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 gap-6 mb-6">
|
|
|
|
|
|
<!-- 系统状态监控 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-6">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text">系统监控</p>
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-3 h-3 rounded-full bg-green-500 mr-2 animate-pulse"></div>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">实时监控</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 三个实时监控图表 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
|
|
|
|
<!-- CPU监控 -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div>
|
|
|
|
|
|
<span class="text-sm font-medium text-dashboard-text">CPU</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span id="cpuPercent" class="text-lg font-bold text-dashboard-text">45%</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-24">
|
|
|
|
|
|
<canvas id="cpuChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- GPU监控 -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div>
|
|
|
|
|
|
<span class="text-sm font-medium text-dashboard-text">GPU</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span id="gpuPercent" class="text-lg font-bold text-dashboard-text">67%</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-24">
|
|
|
|
|
|
<canvas id="gpuChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 内存监控 -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-3 h-3 rounded-full bg-blue-500 mr-2"></div>
|
|
|
|
|
|
<span class="text-sm font-medium text-dashboard-text">内存</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span id="memoryPercent" class="text-lg font-bold text-dashboard-text">52%</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-24">
|
|
|
|
|
|
<canvas id="memoryChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 图表区域 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
|
|
|
|
<!-- 训练进度折线图 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold mb-4 text-dashboard-text">训练进度</p>
|
|
|
|
|
|
<div class="w-full h-60">
|
|
|
|
|
|
<canvas id="trainingChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型分布柱状图 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text">模型类型分布</p>
|
|
|
|
|
|
<div class="flex space-x-2">
|
|
|
|
|
|
<div class="px-3 py-1 bg-dashboard-primary/10 rounded text-xs text-dashboard-primary">全部</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="w-full h-40 mb-4">
|
|
|
|
|
|
<canvas id="modelChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-2 text-center">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p class="text-lg font-bold text-dashboard-text">GPT</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight">8个</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p class="text-lg font-bold text-dashboard-text">BERT</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight">5个</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p class="text-lg font-bold text-dashboard-text">LLaMA</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight">3个</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部项目和订单 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
|
<!-- 当前任务 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold mb-4 text-dashboard-text">当前训练任务</p>
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">GPT-4微调</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">进行中</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-bar bg-dashboard-bg">
|
|
|
|
|
|
<div class="w-[75%] gradient-blue"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-right text-xs text-dashboard-textLight mt-1">75%</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">BERT情感分析</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">队列中</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-bar bg-dashboard-bg">
|
|
|
|
|
|
<div class="w-[0%] gradient-blue"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-right text-xs text-dashboard-textLight mt-1">0%</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">LLaMA对话模型</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">已完成</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-bar bg-dashboard-bg">
|
|
|
|
|
|
<div class="w-[100%] gradient-blue"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-right text-xs text-dashboard-textLight mt-1">100%</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">图像分类模型</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">已完成</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-bar bg-dashboard-bg">
|
|
|
|
|
|
<div class="w-[100%] gradient-blue"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-right text-xs text-dashboard-textLight mt-1">100%</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">推荐系统优化</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">已完成</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-bar bg-dashboard-bg">
|
|
|
|
|
|
<div class="w-[100%] gradient-blue"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-right text-xs text-dashboard-textLight mt-1">100%</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 最近活动 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold mb-4 text-dashboard-text">最近活动</p>
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-1">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">模型训练完成</p>
|
|
|
|
|
|
<p class="text-xs bg-green-100 text-green-600 px-2 py-1 rounded">成功</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">GPT-4微调任务已完成</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight mt-1">2分钟前</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-1">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">新任务开始</p>
|
|
|
|
|
|
<p class="text-xs bg-blue-100 text-blue-600 px-2 py-1 rounded">进行中</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">BERT情感分析模型开始训练</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight mt-1">5分钟前</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-1">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">模型部署</p>
|
|
|
|
|
|
<p class="text-xs bg-green-100 text-green-600 px-2 py-1 rounded">成功</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">LLaMA对话模型已部署到云端</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight mt-1">10分钟前</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="border-b border-dashboard-bg pb-4">
|
|
|
|
|
|
<div class="flex justify-between mb-1">
|
|
|
|
|
|
<p class="font-medium text-dashboard-text">数据集更新</p>
|
|
|
|
|
|
<p class="text-xs bg-purple-100 text-purple-600 px-2 py-1 rounded">已完成</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-dashboard-textLight text-sm">训练数据集已更新</p>
|
|
|
|
|
|
<p class="text-xs text-dashboard-textLight mt-1">30分钟前</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 数据集管理页面 -->
|
|
|
|
|
|
<div id="dataset-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<div class="flex justify-between items-center mb-6">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text">数据集管理</p>
|
|
|
|
|
|
<button onclick="document.getElementById('fileInput').click()" class="px-4 py-2 gradient-blue text-white rounded-lg hover:opacity-90 transition-opacity">
|
|
|
|
|
|
上传数据集
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<input type="file" id="fileInput" style="display: none" accept=".json,.jsonl" onchange="handleFileUpload(this)">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 数据集列表 -->
|
|
|
|
|
|
<div id="datasetList" class="space-y-4">
|
|
|
|
|
|
<!-- 数据集列表将通过 JavaScript 动态加载 -->
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 数据预处理页面 - Easy-Datasets -->
|
|
|
|
|
|
<div id="preprocess-page" class="page-content hidden h-full" style="padding: 0;">
|
|
|
|
|
|
<iframe id="easy-datasets-frame"
|
|
|
|
|
|
src="http://localhost:1717"
|
|
|
|
|
|
style="width: 100%; height: 100%; border: none;"
|
|
|
|
|
|
title="Easy-Datasets">
|
|
|
|
|
|
</iframe>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="pretrain-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text mb-4">模型预训练</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight">模型预训练功能开发中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="finetune-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text mb-4">模型微调</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight">模型微调功能开发中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="rl-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text mb-4">强化训练</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight">强化训练功能开发中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="validate-page" class="page-content hidden" style="padding: 1.5rem; height: 100%; overflow-y: auto;">
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 card-shadow">
|
|
|
|
|
|
<p class="text-lg font-semibold text-dashboard-text mb-4">模型验证</p>
|
|
|
|
|
|
<p class="text-dashboard-textLight">模型验证功能开发中...</p>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型对比页面 -->
|
|
|
|
|
|
<div id="compare-page" class="page-content hidden" style="padding: 0; height: 100%; overflow: hidden;">
|
|
|
|
|
|
<div class="flex flex-col h-full bg-gray-50">
|
|
|
|
|
|
<!-- 顶部配置区 -->
|
|
|
|
|
|
<div id="compare-config-section" class="bg-white border-b border-gray-200 p-4 transition-all duration-300" style="flex-shrink: 0;">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h2 class="text-2xl font-bold text-dashboard-text">模型对比</h2>
|
|
|
|
|
|
<button id="toggleConfigBtn" onclick="toggleConfigSection()" class="flex items-center px-3 py-2 text-sm bg-dashboard-primary text-white rounded-lg hover:bg-dashboard-primary/90 transition-all duration-300 hover:shadow-lg">
|
|
|
|
|
|
<svg id="toggleIcon" class="w-4 h-4 mr-2 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span id="toggleConfigText">折叠配置</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型选择区 -->
|
|
|
|
|
|
<div class="grid-1x4 mb-4">
|
|
|
|
|
|
<!-- 模型A -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-xl p-3 border-2 border-dashboard-primary/20">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text mb-3 flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-dashboard-primary text-white flex items-center justify-center text-sm font-bold mr-2">A</span>
|
|
|
|
|
|
模型 A
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">选择模型</label>
|
|
|
|
|
|
<select id="modelASelect" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择模型A</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型信息</label>
|
|
|
|
|
|
<div id="modelAInfo" class="text-sm text-dashboard-textLight bg-white p-3 rounded-lg border border-gray-200">
|
|
|
|
|
|
请先选择模型
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型B -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-xl p-3 border-2 border-dashboard-accent/20">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text mb-3 flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-dashboard-accent text-white flex items-center justify-center text-sm font-bold mr-2">B</span>
|
|
|
|
|
|
模型 B
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">选择模型</label>
|
|
|
|
|
|
<select id="modelBSelect" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择模型B</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型信息</label>
|
|
|
|
|
|
<div id="modelBInfo" class="text-sm text-dashboard-textLight bg-white p-3 rounded-lg border border-gray-200">
|
|
|
|
|
|
请先选择模型
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型C -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-xl p-3 border-2 border-gray-300 opacity-50 transition-opacity hover:opacity-100">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text mb-3 flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-gray-500 text-white flex items-center justify-center text-sm font-bold mr-2">C</span>
|
|
|
|
|
|
模型 C
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">选择模型</label>
|
|
|
|
|
|
<select id="modelCSelect" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择模型C (可选)</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型信息</label>
|
|
|
|
|
|
<div id="modelCInfo" class="text-sm text-dashboard-textLight bg-white p-3 rounded-lg border border-gray-200">
|
|
|
|
|
|
请先选择模型
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型D -->
|
|
|
|
|
|
<div class="bg-gray-50 rounded-xl p-3 border-2 border-gray-300 opacity-50 transition-opacity hover:opacity-100">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text mb-3 flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-gray-500 text-white flex items-center justify-center text-sm font-bold mr-2">D</span>
|
|
|
|
|
|
模型 D
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">选择模型</label>
|
|
|
|
|
|
<select id="modelDSelect" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择模型D (可选)</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型信息</label>
|
|
|
|
|
|
<div id="modelDInfo" class="text-sm text-dashboard-textLight bg-white p-3 rounded-lg border border-gray-200">
|
|
|
|
|
|
请先选择模型
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 测试输入区 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 border border-gray-200 mb-4">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text mb-3">测试输入</h3>
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">提示词</label>
|
|
|
|
|
|
<textarea id="testPrompt" rows="3" placeholder="输入您想要测试的提示词..."
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20"></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-1">Temperature</label>
|
|
|
|
|
|
<input type="range" id="testTemperature" min="0" max="2" step="0.1" value="0.7" class="w-full">
|
|
|
|
|
|
<div class="flex justify-between text-xs text-dashboard-textLight mt-1">
|
|
|
|
|
|
<span>保守</span>
|
|
|
|
|
|
<span id="tempDisplay">0.7</span>
|
|
|
|
|
|
<span>创意</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-1">最大Token数</label>
|
|
|
|
|
|
<input type="number" id="testMaxTokens" value="1024" min="1" max="32768" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
|
|
|
<div class="flex justify-center">
|
|
|
|
|
|
<button onclick="runModelComparison()" id="compareBtn" class="px-8 py-3 bg-gradient-to-r from-dashboard-primary to-dashboard-secondary text-white rounded-lg hover:opacity-90 transition-opacity font-medium">
|
|
|
|
|
|
<svg class="w-5 h-5 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
开始对比
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 结果展示区 -->
|
|
|
|
|
|
<div class="flex-1 overflow-y-auto p-4" style="min-height: 400px; position: relative;">
|
|
|
|
|
|
<!-- 展开配置按钮 -->
|
|
|
|
|
|
<button id="showConfigBtn" onclick="showConfigSection()" class="absolute top-4 right-4 z-20 hidden flex items-center px-4 py-3 text-base bg-gradient-to-r from-dashboard-primary to-dashboard-secondary text-white rounded-xl hover:opacity-90 transition-all duration-300 shadow-xl hover:shadow-2xl transform hover:scale-105" style="display: none;">
|
|
|
|
|
|
<svg class="w-5 h-5 mr-2 animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span class="font-semibold">显示配置</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div id="comparisonResults" class="hidden">
|
|
|
|
|
|
<div class="grid-2x2">
|
|
|
|
|
|
<!-- 模型A输出 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 border border-gray-200">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-dashboard-primary text-white flex items-center justify-center text-sm font-bold mr-2">A</span>
|
|
|
|
|
|
<span id="modelAName">模型A</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="flex space-x-2">
|
|
|
|
|
|
<button onclick="copyToClipboard('modelAOutput')" class="text-dashboard-textLight hover:text-dashboard-primary">
|
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight mb-2">输出结果</div>
|
|
|
|
|
|
<div id="modelAOutput" class="bg-gray-50 p-4 rounded-lg border border-gray-200" style="min-height: 300px; max-height: 600px; overflow-y: auto;">
|
|
|
|
|
|
<div class="text-dashboard-textLight">等待对比结果...</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight">
|
|
|
|
|
|
<div>响应时间: <span id="modelATime">-</span></div>
|
|
|
|
|
|
<div>Token数: <span id="modelATokens">-</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型B输出 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 border border-gray-200">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-dashboard-accent text-white flex items-center justify-center text-sm font-bold mr-2">B</span>
|
|
|
|
|
|
<span id="modelBName">模型B</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="flex space-x-2">
|
|
|
|
|
|
<button onclick="copyToClipboard('modelBOutput')" class="text-dashboard-textLight hover:text-dashboard-primary">
|
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight mb-2">输出结果</div>
|
|
|
|
|
|
<div id="modelBOutput" class="bg-gray-50 p-4 rounded-lg border border-gray-200" style="min-height: 300px; max-height: 600px; overflow-y: auto;">
|
|
|
|
|
|
<div class="text-dashboard-textLight">等待对比结果...</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight">
|
|
|
|
|
|
<div>响应时间: <span id="modelBTime">-</span></div>
|
|
|
|
|
|
<div>Token数: <span id="modelBTokens">-</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型C输出 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 border border-gray-200 hidden" id="modelCOutputCard">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-gray-500 text-white flex items-center justify-center text-sm font-bold mr-2">C</span>
|
|
|
|
|
|
<span id="modelCName">模型C</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="flex space-x-2">
|
|
|
|
|
|
<button onclick="copyToClipboard('modelCOutput')" class="text-dashboard-textLight hover:text-dashboard-primary">
|
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight mb-2">输出结果</div>
|
|
|
|
|
|
<div id="modelCOutput" class="bg-gray-50 p-4 rounded-lg border border-gray-200" style="min-height: 300px; max-height: 600px; overflow-y: auto;">
|
|
|
|
|
|
<div class="text-dashboard-textLight">等待对比结果...</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight">
|
|
|
|
|
|
<div>响应时间: <span id="modelCTime">-</span></div>
|
|
|
|
|
|
<div>Token数: <span id="modelCTokens">-</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型D输出 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-4 border border-gray-200 hidden" id="modelDOutputCard">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h3 class="text-lg font-semibold text-dashboard-text flex items-center">
|
|
|
|
|
|
<span class="w-6 h-6 rounded-full bg-gray-500 text-white flex items-center justify-center text-sm font-bold mr-2">D</span>
|
|
|
|
|
|
<span id="modelDName">模型D</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="flex space-x-2">
|
|
|
|
|
|
<button onclick="copyToClipboard('modelDOutput')" class="text-dashboard-textLight hover:text-dashboard-primary">
|
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight mb-2">输出结果</div>
|
|
|
|
|
|
<div id="modelDOutput" class="bg-gray-50 p-4 rounded-lg border border-gray-200" style="min-height: 300px; max-height: 600px; overflow-y: auto;">
|
|
|
|
|
|
<div class="text-dashboard-textLight">等待对比结果...</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-xs text-dashboard-textLight">
|
|
|
|
|
|
<div>响应时间: <span id="modelDTime">-</span></div>
|
|
|
|
|
|
<div>Token数: <span id="modelDTokens">-</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<!-- 默认状态 -->
|
|
|
|
|
|
<div id="comparisonDefault" class="h-full flex items-center justify-center">
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="w-24 h-24 rounded-full bg-dashboard-primary/10 flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
|
<svg class="w-12 h-12 text-dashboard-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
|
|
|
|
|
</svg>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<h3 class="text-xl font-semibold text-dashboard-text mb-2">开始模型对比</h3>
|
|
|
|
|
|
<p class="text-dashboard-textLight">选择两个模型,输入提示词,开始您的对比测试</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型配置页面 -->
|
|
|
|
|
|
<div id="config-page" class="page-content hidden" style="padding: 0; height: 100%; overflow: hidden;">
|
|
|
|
|
|
<div class="flex h-full bg-gray-50">
|
|
|
|
|
|
<!-- 左侧模型列表 -->
|
|
|
|
|
|
<div class="w-1/3 bg-white border-r border-gray-200 flex flex-col">
|
|
|
|
|
|
<div class="p-6 border-b border-gray-200">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h2 class="text-xl font-semibold text-dashboard-text">模型配置</h2>
|
|
|
|
|
|
<button onclick="showAddModelModal()" class="px-4 py-2 bg-dashboard-primary text-white rounded-lg hover:bg-dashboard-primary/90 transition-colors text-sm">
|
|
|
|
|
|
+ 添加模型
|
|
|
|
|
|
</button>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<div class="relative">
|
|
|
|
|
|
<input type="text" id="modelSearch" placeholder="搜索模型..."
|
|
|
|
|
|
class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20"
|
|
|
|
|
|
onkeyup="filterModels()">
|
|
|
|
|
|
<svg class="w-4 h-4 absolute right-3 top-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
|
|
|
|
</svg>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-13 12:09:41 +08:00
|
|
|
|
<!-- 模型列表 -->
|
|
|
|
|
|
<div id="modelList" class="flex-1 overflow-y-auto">
|
|
|
|
|
|
<!-- 模型项将通过JavaScript动态加载 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧配置详情 -->
|
|
|
|
|
|
<div class="flex-1 flex flex-col">
|
|
|
|
|
|
<div id="modelConfigPanel" class="flex-1 overflow-y-auto">
|
|
|
|
|
|
<!-- 默认状态 -->
|
|
|
|
|
|
<div id="config-default" class="h-full flex items-center justify-center">
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="w-24 h-24 rounded-full bg-dashboard-primary/10 flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
|
<svg class="w-12 h-12 text-dashboard-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h3 class="text-xl font-semibold text-dashboard-text mb-2">选择模型配置</h3>
|
|
|
|
|
|
<p class="text-dashboard-textLight">从左侧选择一个模型进行配置,或添加新模型</p>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 模型配置表单 -->
|
|
|
|
|
|
<div id="config-form" class="hidden h-full">
|
|
|
|
|
|
<!-- 顶部操作栏 -->
|
|
|
|
|
|
<div class="bg-white border-b border-gray-200 p-6">
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 id="modelName" class="text-2xl font-bold text-dashboard-text"></h3>
|
|
|
|
|
|
<div class="flex items-center mt-2">
|
|
|
|
|
|
<span id="modelStatus" class="px-2 py-1 text-xs rounded bg-gray-100 text-gray-600 mr-2">未测试</span>
|
|
|
|
|
|
<span id="modelProvider" class="text-sm text-dashboard-textLight"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex space-x-3">
|
|
|
|
|
|
<button onclick="testModelConnection()" id="testBtn" class="px-4 py-2 border border-dashboard-primary text-dashboard-primary rounded-lg hover:bg-dashboard-primary/5 transition-colors">
|
|
|
|
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
测试连接
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button onclick="saveModelConfig()" class="px-4 py-2 bg-dashboard-primary text-white rounded-lg hover:bg-dashboard-primary/90 transition-colors">
|
|
|
|
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
保存配置
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button onclick="deleteModel()" class="px-4 py-2 border border-red-300 text-red-600 rounded-lg hover:bg-red-50 transition-colors">
|
|
|
|
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
删除
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 配置表单 -->
|
|
|
|
|
|
<div class="p-6 space-y-6">
|
|
|
|
|
|
<!-- 基本信息 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
|
|
|
|
|
<h4 class="text-lg font-semibold text-dashboard-text mb-4">基本信息</h4>
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型名称</label>
|
|
|
|
|
|
<input type="text" id="configModelName" placeholder="输入模型名称"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型类型</label>
|
|
|
|
|
|
<select id="configModelType" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择模型类型</option>
|
|
|
|
|
|
<option value="gpt">GPT 系列</option>
|
|
|
|
|
|
<option value="bert">BERT 系列</option>
|
|
|
|
|
|
<option value="llama">LLaMA 系列</option>
|
|
|
|
|
|
<option value="claude">Claude 系列</option>
|
|
|
|
|
|
<option value="custom">自定义</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">提供方</label>
|
|
|
|
|
|
<select id="configProvider" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
<option value="">请选择提供方</option>
|
|
|
|
|
|
<option value="openai">OpenAI</option>
|
|
|
|
|
|
<option value="anthropic">Anthropic</option>
|
|
|
|
|
|
<option value="azure">Azure OpenAI</option>
|
|
|
|
|
|
<option value="local">本地部署</option>
|
|
|
|
|
|
<option value="custom">自定义</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">模型版本</label>
|
|
|
|
|
|
<input type="text" id="configModelVersion" placeholder="例如:gpt-3.5-turbo"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- API配置 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
|
|
|
|
|
<h4 class="text-lg font-semibold text-dashboard-text mb-4">API 配置</h4>
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">API 地址</label>
|
|
|
|
|
|
<input type="text" id="configApiUrl" placeholder="https://api.openai.com/v1"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">API 密钥</label>
|
|
|
|
|
|
<input type="password" id="configApiKey" placeholder="输入 API 密钥"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">超时时间(秒)</label>
|
|
|
|
|
|
<input type="number" id="configTimeout" value="30" min="1" max="300"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">最大重试次数</label>
|
|
|
|
|
|
<input type="number" id="configMaxRetries" value="3" min="0" max="10"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模型参数 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
|
|
|
|
|
<h4 class="text-lg font-semibold text-dashboard-text mb-4">模型参数</h4>
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">
|
|
|
|
|
|
Temperature: <span id="tempValue">0.7</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<input type="range" id="configTemperature" min="0" max="2" step="0.1" value="0.7"
|
|
|
|
|
|
oninput="document.getElementById('tempValue').textContent = this.value"
|
|
|
|
|
|
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
|
|
|
|
|
<div class="flex justify-between text-xs text-dashboard-textLight mt-1">
|
|
|
|
|
|
<span>保守</span>
|
|
|
|
|
|
<span>平衡</span>
|
|
|
|
|
|
<span>创意</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">
|
|
|
|
|
|
Top-p: <span id="topPValue">1.0</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<input type="range" id="configTopP" min="0" max="1" step="0.1" value="1.0"
|
|
|
|
|
|
oninput="document.getElementById('topPValue').textContent = this.value"
|
|
|
|
|
|
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">
|
|
|
|
|
|
Top-k: <span id="topKValue">50</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<input type="range" id="configTopK" min="1" max="100" step="1" value="50"
|
|
|
|
|
|
oninput="document.getElementById('topKValue').textContent = this.value"
|
|
|
|
|
|
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">最大 Token 数</label>
|
|
|
|
|
|
<input type="number" id="configMaxTokens" value="2048" min="1" max="32768"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-dashboard-text mb-2">系统提示词</label>
|
|
|
|
|
|
<textarea id="configSystemPrompt" rows="3" placeholder="输入系统提示词(可选)"
|
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-dashboard-primary/20"></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 高级设置 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
|
|
|
|
|
<h4 class="text-lg font-semibold text-dashboard-text mb-4">高级设置</h4>
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<input type="checkbox" id="configStreaming" checked class="w-4 h-4 text-dashboard-primary focus:ring-dashboard-primary/20 border-gray-300 rounded">
|
|
|
|
|
|
<label for="configStreaming" class="ml-2 text-sm text-dashboard-text">启用流式输出</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<input type="checkbox" id="configFunctions" class="w-4 h-4 text-dashboard-primary focus:ring-dashboard-primary/20 border-gray-300 rounded">
|
|
|
|
|
|
<label for="configFunctions" class="ml-2 text-sm text-dashboard-text">启用函数调用</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<input type="checkbox" id="configLogRequests" class="w-4 h-4 text-dashboard-primary focus:ring-dashboard-primary/20 border-gray-300 rounded">
|
|
|
|
|
|
<label for="configLogRequests" class="ml-2 text-sm text-dashboard-text">记录请求日志</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</div>
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 图表初始化脚本 -->
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 训练进度折线图
|
|
|
|
|
|
const trainingCtx = document.getElementById('trainingChart').getContext('2d');
|
|
|
|
|
|
new Chart(trainingCtx, {
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: ['第1天', '第2天', '第3天', '第4天', '第5天', '第6天', '第7天'],
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: '准确率',
|
|
|
|
|
|
data: [65, 72, 78, 85, 88, 92, 95],
|
|
|
|
|
|
borderColor: '#6065D9',
|
|
|
|
|
|
backgroundColor: 'rgba(96, 101, 217, 0.1)',
|
|
|
|
|
|
fill: true,
|
|
|
|
|
|
tension: 0.4,
|
|
|
|
|
|
pointBackgroundColor: '#FFFFFF',
|
|
|
|
|
|
pointBorderColor: '#6065D9'
|
|
|
|
|
|
}]
|
|
|
|
|
|
},
|
|
|
|
|
|
options: {
|
|
|
|
|
|
responsive: true,
|
|
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
|
|
plugins: {
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
scales: {
|
|
|
|
|
|
x: {
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
},
|
|
|
|
|
|
ticks: {
|
|
|
|
|
|
color: '#666666'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
y: {
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
color: 'rgba(0, 0, 0, 0.05)'
|
|
|
|
|
|
},
|
|
|
|
|
|
ticks: {
|
|
|
|
|
|
color: '#666666'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 模型类型分布柱状图
|
|
|
|
|
|
const modelCtx = document.getElementById('modelChart').getContext('2d');
|
|
|
|
|
|
new Chart(modelCtx, {
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: ['GPT', 'BERT', 'LLaMA', 'T5', 'Others'],
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: '模型数量',
|
|
|
|
|
|
data: [8, 5, 3, 2, 4],
|
|
|
|
|
|
backgroundColor: [
|
|
|
|
|
|
'rgba(96, 101, 217, 0.8)',
|
|
|
|
|
|
'rgba(118, 75, 162, 0.8)',
|
|
|
|
|
|
'rgba(23, 215, 250, 0.8)',
|
|
|
|
|
|
'rgba(96, 101, 217, 0.6)',
|
|
|
|
|
|
'rgba(118, 75, 162, 0.6)'
|
|
|
|
|
|
],
|
|
|
|
|
|
borderRadius: 4
|
|
|
|
|
|
}]
|
|
|
|
|
|
},
|
|
|
|
|
|
options: {
|
|
|
|
|
|
responsive: true,
|
|
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
|
|
plugins: {
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
scales: {
|
|
|
|
|
|
x: {
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
},
|
|
|
|
|
|
ticks: {
|
|
|
|
|
|
color: '#666666'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
y: {
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
color: 'rgba(0, 0, 0, 0.05)'
|
|
|
|
|
|
},
|
|
|
|
|
|
ticks: {
|
|
|
|
|
|
color: '#666666'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 系统监控图表配置
|
|
|
|
|
|
const chartConfig = {
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
responsive: true,
|
|
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
|
|
animation: false,
|
|
|
|
|
|
plugins: {
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
scales: {
|
|
|
|
|
|
x: {
|
|
|
|
|
|
display: false
|
|
|
|
|
|
},
|
|
|
|
|
|
y: {
|
|
|
|
|
|
display: false,
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: 100
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
elements: {
|
|
|
|
|
|
line: {
|
|
|
|
|
|
tension: 0.4,
|
|
|
|
|
|
borderWidth: 2
|
|
|
|
|
|
},
|
|
|
|
|
|
point: {
|
|
|
|
|
|
radius: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 数据存储
|
|
|
|
|
|
let cpuHistoryData = [];
|
|
|
|
|
|
let gpuHistoryData = [];
|
|
|
|
|
|
let memoryHistoryData = [];
|
|
|
|
|
|
const maxDataPoints = 20;
|
|
|
|
|
|
|
|
|
|
|
|
// 生成模拟系统数据
|
|
|
|
|
|
function generateMockData() {
|
|
|
|
|
|
const base = 50;
|
|
|
|
|
|
const variation = 30;
|
|
|
|
|
|
return {
|
|
|
|
|
|
cpu: Math.floor(base + (Math.random() - 0.5) * variation),
|
|
|
|
|
|
gpu: Math.floor(base + (Math.random() - 0.5) * variation),
|
|
|
|
|
|
memory: Math.floor(base + (Math.random() - 0.5) * variation)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加数据到历史记录
|
|
|
|
|
|
function addToHistory(type, value) {
|
|
|
|
|
|
switch(type) {
|
|
|
|
|
|
case 'cpu':
|
|
|
|
|
|
cpuHistoryData.push(value);
|
|
|
|
|
|
if (cpuHistoryData.length > maxDataPoints) cpuHistoryData.shift();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'gpu':
|
|
|
|
|
|
gpuHistoryData.push(value);
|
|
|
|
|
|
if (gpuHistoryData.length > maxDataPoints) gpuHistoryData.shift();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'memory':
|
|
|
|
|
|
memoryHistoryData.push(value);
|
|
|
|
|
|
if (memoryHistoryData.length > maxDataPoints) memoryHistoryData.shift();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建CPU图表
|
|
|
|
|
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
|
|
|
|
|
const cpuChart = new Chart(cpuCtx, {
|
|
|
|
|
|
...chartConfig,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: Array.from({length: maxDataPoints}, (_, i) => i),
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: 'CPU',
|
|
|
|
|
|
data: new Array(maxDataPoints).fill(0),
|
|
|
|
|
|
borderColor: '#EF4444',
|
|
|
|
|
|
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
|
|
|
|
fill: true
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建GPU图表
|
|
|
|
|
|
const gpuCtx = document.getElementById('gpuChart').getContext('2d');
|
|
|
|
|
|
const gpuChart = new Chart(gpuCtx, {
|
|
|
|
|
|
...chartConfig,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: Array.from({length: maxDataPoints}, (_, i) => i),
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: 'GPU',
|
|
|
|
|
|
data: new Array(maxDataPoints).fill(0),
|
|
|
|
|
|
borderColor: '#10B981',
|
|
|
|
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
|
|
|
|
fill: true
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建内存图表
|
|
|
|
|
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
|
|
|
|
|
const memoryChart = new Chart(memoryCtx, {
|
|
|
|
|
|
...chartConfig,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: Array.from({length: maxDataPoints}, (_, i) => i),
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: 'Memory',
|
|
|
|
|
|
data: new Array(maxDataPoints).fill(0),
|
|
|
|
|
|
borderColor: '#3B82F6',
|
|
|
|
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
|
|
|
|
fill: true
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表数据
|
|
|
|
|
|
function updateCharts() {
|
|
|
|
|
|
// 确保数据长度一致
|
|
|
|
|
|
while (cpuHistoryData.length < maxDataPoints) {
|
|
|
|
|
|
cpuHistoryData.unshift(0);
|
|
|
|
|
|
gpuHistoryData.unshift(0);
|
|
|
|
|
|
memoryHistoryData.unshift(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表数据
|
|
|
|
|
|
cpuChart.data.datasets[0].data = cpuHistoryData;
|
|
|
|
|
|
gpuChart.data.datasets[0].data = gpuHistoryData;
|
|
|
|
|
|
memoryChart.data.datasets[0].data = memoryHistoryData;
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新图表
|
|
|
|
|
|
cpuChart.update('none');
|
|
|
|
|
|
gpuChart.update('none');
|
|
|
|
|
|
memoryChart.update('none');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化并开始模拟监控
|
|
|
|
|
|
function initSystemMonitoring() {
|
|
|
|
|
|
// 生成初始数据
|
|
|
|
|
|
for (let i = 0; i < maxDataPoints; i++) {
|
|
|
|
|
|
const data = generateMockData();
|
|
|
|
|
|
addToHistory('cpu', data.cpu);
|
|
|
|
|
|
addToHistory('gpu', data.gpu);
|
|
|
|
|
|
addToHistory('memory', data.memory);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置定期更新
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
const data = generateMockData();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新百分比显示
|
|
|
|
|
|
document.getElementById('cpuPercent').textContent = data.cpu + '%';
|
|
|
|
|
|
document.getElementById('gpuPercent').textContent = data.gpu + '%';
|
|
|
|
|
|
document.getElementById('memoryPercent').textContent = data.memory + '%';
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到历史数据
|
|
|
|
|
|
addToHistory('cpu', data.cpu);
|
|
|
|
|
|
addToHistory('gpu', data.gpu);
|
|
|
|
|
|
addToHistory('memory', data.memory);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表
|
|
|
|
|
|
updateCharts();
|
|
|
|
|
|
}, 2000); // 每2秒更新一次
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后启动监控
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initSystemMonitoring);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 页面导航功能
|
|
|
|
|
|
function switchPage(pageId) {
|
|
|
|
|
|
// 隐藏所有页面
|
|
|
|
|
|
const allPages = document.querySelectorAll('.page-content');
|
|
|
|
|
|
allPages.forEach(page => {
|
|
|
|
|
|
page.classList.add('hidden');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示选中的页面
|
|
|
|
|
|
const targetPage = document.getElementById(pageId + '-page');
|
|
|
|
|
|
if (targetPage) {
|
|
|
|
|
|
targetPage.classList.remove('hidden');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新侧边栏激活状态
|
|
|
|
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
|
|
|
|
navItems.forEach(item => {
|
|
|
|
|
|
item.classList.remove('gradient-blue', 'text-white');
|
|
|
|
|
|
item.classList.add('hover:bg-dashboard-primary/10', 'text-dashboard-textLight');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 激活当前页面
|
|
|
|
|
|
const activeItem = document.querySelector(`[data-page="${pageId}"]`);
|
|
|
|
|
|
if (activeItem) {
|
|
|
|
|
|
activeItem.classList.remove('hover:bg-dashboard-primary/10', 'text-dashboard-textLight');
|
|
|
|
|
|
activeItem.classList.add('gradient-blue', 'text-white');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果切换到配置页面,初始化模型配置
|
|
|
|
|
|
if (pageId === 'config') {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
initModelConfig();
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果切换到对比页面,初始化模型对比
|
|
|
|
|
|
if (pageId === 'compare') {
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
await initModelComparison();
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化导航
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
// 默认显示仪表盘
|
|
|
|
|
|
switchPage('dashboard');
|
|
|
|
|
|
|
|
|
|
|
|
// 为所有导航项添加点击事件
|
|
|
|
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
|
|
|
|
navItems.forEach(item => {
|
|
|
|
|
|
item.addEventListener('click', function(e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const pageId = this.getAttribute('data-page');
|
|
|
|
|
|
switchPage(pageId);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 修改 switchPage 函数以支持数据集页面加载
|
|
|
|
|
|
const originalSwitchPage = switchPage;
|
|
|
|
|
|
switchPage = function(pageId) {
|
|
|
|
|
|
originalSwitchPage(pageId);
|
|
|
|
|
|
// 如果切换到数据集页面,加载数据集列表
|
|
|
|
|
|
if (pageId === 'dataset') {
|
|
|
|
|
|
loadDatasets();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 数据集管理相关功能
|
|
|
|
|
|
const API_BASE = 'http://localhost:3000';
|
|
|
|
|
|
|
|
|
|
|
|
// 加载数据集列表
|
|
|
|
|
|
async function loadDatasets() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/datasets`);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
// 适配StandardResponse格式: {status: 1, response: {...}}
|
|
|
|
|
|
if (result.status === 1 && result.response && result.response.datasets) {
|
|
|
|
|
|
renderDatasetList(result.response.datasets);
|
|
|
|
|
|
} else if (result.status === 0) {
|
|
|
|
|
|
console.error('加载数据集列表失败:', result.response?.error || '未知错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载数据集列表失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染数据集列表
|
|
|
|
|
|
function renderDatasetList(datasets) {
|
|
|
|
|
|
const container = document.getElementById('datasetList');
|
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
datasets.forEach(dataset => {
|
|
|
|
|
|
const statusClass = getStatusClass(dataset.status);
|
|
|
|
|
|
const statusText = dataset.status;
|
|
|
|
|
|
|
|
|
|
|
|
const datasetDiv = document.createElement('div');
|
|
|
|
|
|
datasetDiv.className = 'border border-dashboard-bg rounded-lg p-4 hover:shadow-md transition-shadow';
|
|
|
|
|
|
datasetDiv.innerHTML = `
|
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 class="font-medium text-dashboard-text">${dataset.name}</h3>
|
|
|
|
|
|
<p class="text-sm text-dashboard-textLight mt-1">${dataset.size_display || dataset.size_mb + ' MB' || '未知大小'} • ${dataset.description || '无描述'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center space-x-2">
|
|
|
|
|
|
<span class="px-2 py-1 ${statusClass} text-xs rounded">${statusText}</span>
|
|
|
|
|
|
<button onclick="viewDatasetDetails('${dataset.file_id}')" class="text-dashboard-primary hover:underline text-sm">查看详情</button>
|
|
|
|
|
|
<button onclick="deleteDataset('${dataset.file_id}', '${dataset.name}')" class="text-red-500 hover:text-red-700 text-sm">删除</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
container.appendChild(datasetDiv);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态样式类
|
|
|
|
|
|
function getStatusClass(status) {
|
|
|
|
|
|
switch(status) {
|
|
|
|
|
|
case '已处理':
|
|
|
|
|
|
return 'bg-green-100 text-green-600';
|
|
|
|
|
|
case '处理中':
|
|
|
|
|
|
return 'bg-blue-100 text-blue-600';
|
|
|
|
|
|
case '待处理':
|
|
|
|
|
|
return 'bg-gray-100 text-gray-600';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'bg-gray-100 text-gray-600';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看数据集详情
|
|
|
|
|
|
// 处理文件上传
|
|
|
|
|
|
async function handleFileUpload(input) {
|
|
|
|
|
|
const file = input.files[0];
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件类型
|
|
|
|
|
|
const allowedTypes = ['.json', '.jsonl'];
|
|
|
|
|
|
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
if (!allowedTypes.includes(fileExtension)) {
|
|
|
|
|
|
alert('只支持 JSON 和 JSONL 格式的文件!');
|
|
|
|
|
|
input.value = '';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件大小 (100MB)
|
|
|
|
|
|
const maxSize = 100 * 1024 * 1024;
|
|
|
|
|
|
if (file.size > maxSize) {
|
|
|
|
|
|
alert('文件大小不能超过 100MB!');
|
|
|
|
|
|
input.value = '';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append('file', file);
|
|
|
|
|
|
formData.append('description', `用户上传的数据集文件: ${file.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/datasets/upload`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: formData
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
// 适配StandardResponse格式: {status: 1, response: {...}}
|
|
|
|
|
|
if (result.status === 1) {
|
|
|
|
|
|
alert('上传成功!');
|
|
|
|
|
|
// 重新加载数据集列表
|
|
|
|
|
|
loadDatasets();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const errorMsg = result.response?.error || result.message || '未知错误';
|
|
|
|
|
|
alert('上传失败: ' + errorMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('上传失败:', error);
|
|
|
|
|
|
alert('上传失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空文件选择
|
|
|
|
|
|
input.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 退出登录函数
|
|
|
|
|
|
function logout() {
|
|
|
|
|
|
// 清除登录状态
|
|
|
|
|
|
sessionStorage.removeItem('isLoggedIn');
|
|
|
|
|
|
// 跳转到登录页面
|
|
|
|
|
|
window.location.href = 'login.html';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看数据集详情
|
|
|
|
|
|
async function viewDatasetDetails(fileId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 调用API获取文件内容
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/datasets/${fileId}/content?limit=5`);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.status === 1) {
|
|
|
|
|
|
const data = result.response;
|
|
|
|
|
|
let contentHtml = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(data.preview)) {
|
|
|
|
|
|
// 如果是数组,显示前5条记录
|
|
|
|
|
|
contentHtml = '<div style="max-height: 400px; overflow-y: auto;"><pre>' +
|
|
|
|
|
|
JSON.stringify(data.preview, null, 2) +
|
|
|
|
|
|
'</pre></div>';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果是对象,直接显示
|
|
|
|
|
|
contentHtml = '<div style="max-height: 400px; overflow-y: auto;"><pre>' +
|
|
|
|
|
|
JSON.stringify(data.preview, null, 2) +
|
|
|
|
|
|
'</pre></div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示模态框
|
|
|
|
|
|
showDatasetModal(data.filename, contentHtml);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('获取文件内容失败: ' + (result.response?.error || '未知错误'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取文件详情失败:', error);
|
|
|
|
|
|
alert('获取文件详情失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示数据集详情模态框
|
|
|
|
|
|
function showDatasetModal(title, content) {
|
|
|
|
|
|
const modal = document.createElement('div');
|
|
|
|
|
|
modal.style.cssText = `
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
z-index: 10000;
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const modalContent = document.createElement('div');
|
|
|
|
|
|
modalContent.style.cssText = `
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
|
max-height: 600px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const modalHeader = document.createElement('div');
|
|
|
|
|
|
modalHeader.style.cssText = `
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
border-bottom: 1px solid #eee;
|
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const titleElement = document.createElement('h3');
|
|
|
|
|
|
titleElement.textContent = title;
|
|
|
|
|
|
titleElement.style.cssText = 'margin: 0; color: #333;';
|
|
|
|
|
|
|
|
|
|
|
|
const closeButton = document.createElement('button');
|
|
|
|
|
|
closeButton.textContent = '×';
|
|
|
|
|
|
closeButton.style.cssText = `
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
`;
|
|
|
|
|
|
closeButton.onmouseover = () => closeButton.style.background = '#f0f0f0';
|
|
|
|
|
|
closeButton.onmouseout = () => closeButton.style.background = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
const closeModal = () => {
|
|
|
|
|
|
document.body.removeChild(modal);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
closeButton.onclick = closeModal;
|
|
|
|
|
|
modal.onclick = (e) => {
|
|
|
|
|
|
if (e.target === modal) closeModal();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
modalHeader.appendChild(titleElement);
|
|
|
|
|
|
modalHeader.appendChild(closeButton);
|
|
|
|
|
|
|
|
|
|
|
|
const contentDiv = document.createElement('div');
|
|
|
|
|
|
contentDiv.innerHTML = content;
|
|
|
|
|
|
|
|
|
|
|
|
modalContent.appendChild(modalHeader);
|
|
|
|
|
|
modalContent.appendChild(contentDiv);
|
|
|
|
|
|
|
|
|
|
|
|
modal.appendChild(modalContent);
|
|
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除数据集
|
|
|
|
|
|
async function deleteDataset(fileId, filename) {
|
|
|
|
|
|
// 确认删除对话框
|
|
|
|
|
|
const confirmMessage = `确定要删除数据集 "${filename}" 吗?\n\n此操作不可撤销!`;
|
|
|
|
|
|
|
|
|
|
|
|
if (!confirm(confirmMessage)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/datasets/${fileId}`, {
|
|
|
|
|
|
method: 'DELETE'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.status === 1) {
|
|
|
|
|
|
alert('删除成功!');
|
|
|
|
|
|
// 重新加载数据集列表
|
|
|
|
|
|
loadDatasets();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('删除失败: ' + (result.response?.error || '未知错误'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除失败:', error);
|
|
|
|
|
|
alert('删除失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 模型配置相关功能 ============
|
|
|
|
|
|
|
|
|
|
|
|
// 模型数据
|
|
|
|
|
|
let modelConfigs = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 从API加载模型数据
|
|
|
|
|
|
async function loadModelsFromAPI() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/`);
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
modelConfigs = data.data.models;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('加载模型失败:', data.message);
|
|
|
|
|
|
modelConfigs = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载模型失败:', error);
|
|
|
|
|
|
modelConfigs = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化时加载模型
|
|
|
|
|
|
async function initializeModels() {
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟模型数据(仅作参考)
|
|
|
|
|
|
let mockModelConfigs = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'model-1',
|
|
|
|
|
|
name: 'GPT-4 Turbo',
|
|
|
|
|
|
type: 'gpt',
|
|
|
|
|
|
provider: 'openai',
|
|
|
|
|
|
version: 'gpt-4-turbo',
|
|
|
|
|
|
apiUrl: 'https://api.openai.com/v1',
|
|
|
|
|
|
apiKey: '',
|
|
|
|
|
|
timeout: 30,
|
|
|
|
|
|
maxRetries: 3,
|
|
|
|
|
|
temperature: 0.7,
|
|
|
|
|
|
topP: 1.0,
|
|
|
|
|
|
topK: 50,
|
|
|
|
|
|
maxTokens: 2048,
|
|
|
|
|
|
systemPrompt: '你是一个有用的AI助手。',
|
|
|
|
|
|
streaming: true,
|
|
|
|
|
|
functions: false,
|
|
|
|
|
|
logRequests: true,
|
|
|
|
|
|
status: '已测试'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'model-2',
|
|
|
|
|
|
name: 'Claude-3 Sonnet',
|
|
|
|
|
|
type: 'claude',
|
|
|
|
|
|
provider: 'anthropic',
|
|
|
|
|
|
version: 'claude-3-sonnet-20240229',
|
|
|
|
|
|
apiUrl: 'https://api.anthropic.com/v1',
|
|
|
|
|
|
apiKey: '',
|
|
|
|
|
|
timeout: 30,
|
|
|
|
|
|
maxRetries: 3,
|
|
|
|
|
|
temperature: 0.5,
|
|
|
|
|
|
topP: 0.9,
|
|
|
|
|
|
topK: 40,
|
|
|
|
|
|
maxTokens: 4096,
|
|
|
|
|
|
systemPrompt: '你是一个有用的AI助手。',
|
|
|
|
|
|
streaming: true,
|
|
|
|
|
|
functions: true,
|
|
|
|
|
|
logRequests: true,
|
|
|
|
|
|
status: '未测试'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'model-3',
|
|
|
|
|
|
name: 'LLaMA-2 7B',
|
|
|
|
|
|
type: 'llama',
|
|
|
|
|
|
provider: 'local',
|
|
|
|
|
|
version: 'llama-2-7b-chat',
|
|
|
|
|
|
apiUrl: 'http://localhost:8080/v1',
|
|
|
|
|
|
apiKey: '',
|
|
|
|
|
|
timeout: 60,
|
|
|
|
|
|
maxRetries: 3,
|
|
|
|
|
|
temperature: 0.8,
|
|
|
|
|
|
topP: 0.95,
|
|
|
|
|
|
topK: 60,
|
|
|
|
|
|
maxTokens: 2048,
|
|
|
|
|
|
systemPrompt: '你是一个有用的AI助手。',
|
|
|
|
|
|
streaming: true,
|
|
|
|
|
|
functions: false,
|
|
|
|
|
|
logRequests: false,
|
|
|
|
|
|
status: '已测试'
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let currentModel = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化模型配置页面
|
|
|
|
|
|
async function initModelConfig() {
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载模型列表
|
|
|
|
|
|
function loadModelList() {
|
|
|
|
|
|
const container = document.getElementById('modelList');
|
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (modelConfigs.length === 0) {
|
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
|
<div class="p-6 text-center text-dashboard-textLight">
|
|
|
|
|
|
<svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<p>暂无模型配置</p>
|
|
|
|
|
|
<p class="text-sm">点击"添加模型"开始配置</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
modelConfigs.forEach(model => {
|
|
|
|
|
|
const modelDiv = document.createElement('div');
|
|
|
|
|
|
modelDiv.className = 'border-b border-gray-200 hover:bg-gray-50 cursor-pointer p-4';
|
|
|
|
|
|
modelDiv.onclick = () => selectModel(model);
|
|
|
|
|
|
|
|
|
|
|
|
const statusClass = getModelStatusClass(model.status);
|
|
|
|
|
|
const providerIcon = getProviderIcon(model.provider);
|
|
|
|
|
|
|
|
|
|
|
|
modelDiv.innerHTML = `
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
<div class="flex items-center mb-2">
|
|
|
|
|
|
${providerIcon}
|
|
|
|
|
|
<h3 class="font-medium text-dashboard-text">${model.name}</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-sm text-dashboard-textLight mb-2">${model.version} • ${getProviderName(model.provider)}</p>
|
|
|
|
|
|
<div class="flex items-center space-x-2">
|
|
|
|
|
|
<span class="px-2 py-1 text-xs rounded ${statusClass}">${model.status}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
container.appendChild(modelDiv);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取模型状态样式
|
|
|
|
|
|
function getModelStatusClass(status) {
|
|
|
|
|
|
switch(status) {
|
|
|
|
|
|
case '已测试':
|
|
|
|
|
|
return 'bg-green-100 text-green-600';
|
|
|
|
|
|
case '连接失败':
|
|
|
|
|
|
return 'bg-red-100 text-red-600';
|
|
|
|
|
|
case '未测试':
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'bg-gray-100 text-gray-600';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取提供方图标
|
|
|
|
|
|
function getProviderIcon(provider) {
|
|
|
|
|
|
const icons = {
|
|
|
|
|
|
'openai': '<svg class="w-4 h-4 text-green-500 mr-2" viewBox="0 0 24 24" fill="currentColor"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 6.678c-1.72 0-3.312-.8933-4.613-2.3314l.8049-.3983a4.3688 4.3688 0 0 1 3.7842 2.0807 4.3808 4.3808 0 0 1 .8272 2.0808l-.8021.3953a4.3712 4.3712 0 0 1-3.7842-2.0807 4.3708 4.3708 0 0 1-.8272-2.0808l.8057-.3944a4.3617 4.3617 0 0 1 3.7842 2.0807 4.3766 4.3766 0 0 1 .8272 2.0808l-.8049.3953a4.3766 4.3766 0 0 1-3.7842-2.0807zm9.019-9.4145a4.3608 4.3608 0 0 1-.8272-2.0807l.8057-.3953a4.3708 4.3708 0 0 1 3.7842 2.0807 4.3766 4.3766 0 0 1 .8272 2.0808l-.8021.3944a4.3708 4.3708 0 0 1-3.7842-2.0807 4.3808 4.3808 0 0 1-.8272-2.0808l.8049-.3953a4.3617 4.3617 0 0 1 3.7842 2.0807 4.3766 4.3766 0 0 1 .8272 2.0808l-.8057.3944a4.3708 4.3708 0 0 1-3.7842-2.0807 4.3708 4.3708 0 0 1-.8272-2.0808z"/></svg>',
|
|
|
|
|
|
'anthropic': '<svg class="w-4 h-4 text-orange-500 mr-2" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>',
|
|
|
|
|
|
'azure': '<svg class="w-4 h-4 text-blue-500 mr-2" viewBox="0 0 24 24" fill="currentColor"><path d="M1 9.5C1 4.8056 4.8056 1 9.5 1S18 4.8056 18 9.5 14.1944 18 9.5 18 1 14.1944 1 9.5zM9.5 3C6.4624 3 4 5.4624 4 8.5S6.4624 14 9.5 14 15 11.5376 15 8.5 12.5376 3 9.5 3z"/></svg>',
|
|
|
|
|
|
'local': '<svg class="w-4 h-4 text-purple-500 mr-2" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v8H8V8z"/></svg>',
|
|
|
|
|
|
'custom': '<svg class="w-4 h-4 text-gray-500 mr-2" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>'
|
|
|
|
|
|
};
|
|
|
|
|
|
return icons[provider] || icons['custom'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取提供方名称
|
|
|
|
|
|
function getProviderName(provider) {
|
|
|
|
|
|
const names = {
|
|
|
|
|
|
'openai': 'OpenAI',
|
|
|
|
|
|
'anthropic': 'Anthropic',
|
|
|
|
|
|
'azure': 'Azure OpenAI',
|
|
|
|
|
|
'local': '本地部署',
|
|
|
|
|
|
'custom': '自定义'
|
|
|
|
|
|
};
|
|
|
|
|
|
return names[provider] || '未知';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选择模型
|
|
|
|
|
|
function selectModel(model) {
|
|
|
|
|
|
currentModel = model;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新右侧面板
|
|
|
|
|
|
const defaultView = document.getElementById('config-default');
|
|
|
|
|
|
const form = document.getElementById('config-form');
|
|
|
|
|
|
|
|
|
|
|
|
defaultView.classList.add('hidden');
|
|
|
|
|
|
form.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
// 填充表单数据
|
|
|
|
|
|
document.getElementById('modelName').textContent = model.name;
|
|
|
|
|
|
document.getElementById('modelStatus').textContent = model.status;
|
|
|
|
|
|
document.getElementById('modelStatus').className = `px-2 py-1 text-xs rounded ${getModelStatusClass(model.status)} mr-2`;
|
|
|
|
|
|
document.getElementById('modelProvider').textContent = getProviderName(model.provider);
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('configModelName').value = model.name;
|
|
|
|
|
|
document.getElementById('configModelType').value = model.type;
|
|
|
|
|
|
document.getElementById('configProvider').value = model.provider;
|
|
|
|
|
|
document.getElementById('configModelVersion').value = model.version;
|
|
|
|
|
|
document.getElementById('configApiUrl').value = model.apiUrl;
|
|
|
|
|
|
document.getElementById('configApiKey').value = model.apiKey;
|
|
|
|
|
|
document.getElementById('configTimeout').value = model.timeout;
|
|
|
|
|
|
document.getElementById('configMaxRetries').value = model.maxRetries;
|
|
|
|
|
|
document.getElementById('configTemperature').value = model.temperature;
|
|
|
|
|
|
document.getElementById('configTopP').value = model.topP;
|
|
|
|
|
|
document.getElementById('configTopK').value = model.topK;
|
|
|
|
|
|
document.getElementById('configMaxTokens').value = model.maxTokens;
|
|
|
|
|
|
document.getElementById('configSystemPrompt').value = model.systemPrompt;
|
|
|
|
|
|
document.getElementById('configStreaming').checked = model.streaming;
|
|
|
|
|
|
document.getElementById('configFunctions').checked = model.functions;
|
|
|
|
|
|
document.getElementById('configLogRequests').checked = model.logRequests;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新滑块值显示
|
|
|
|
|
|
document.getElementById('tempValue').textContent = model.temperature;
|
|
|
|
|
|
document.getElementById('topPValue').textContent = model.topP;
|
|
|
|
|
|
document.getElementById('topKValue').textContent = model.topK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示添加模型模态框
|
|
|
|
|
|
async function showAddModelModal() {
|
|
|
|
|
|
const modelName = prompt('请输入模型名称:');
|
|
|
|
|
|
if (!modelName) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
name: modelName,
|
|
|
|
|
|
type: 'gpt',
|
|
|
|
|
|
provider: 'openai',
|
|
|
|
|
|
version: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找新添加的模型
|
|
|
|
|
|
const newModel = modelConfigs.find(m => m.id === data.data.id);
|
|
|
|
|
|
if (newModel) {
|
|
|
|
|
|
selectModel(newModel);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
alert('模型添加成功!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('添加模型失败: ' + data.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('添加模型失败:', error);
|
|
|
|
|
|
alert('添加模型失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存模型配置
|
|
|
|
|
|
async function saveModelConfig() {
|
|
|
|
|
|
if (!currentModel) {
|
|
|
|
|
|
alert('请先选择一个模型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 准备更新数据
|
|
|
|
|
|
const modelData = {
|
|
|
|
|
|
name: document.getElementById('configModelName').value,
|
|
|
|
|
|
type: document.getElementById('configModelType').value,
|
|
|
|
|
|
provider: document.getElementById('configProvider').value,
|
|
|
|
|
|
version: document.getElementById('configModelVersion').value,
|
|
|
|
|
|
apiUrl: document.getElementById('configApiUrl').value,
|
|
|
|
|
|
apiKey: document.getElementById('configApiKey').value,
|
|
|
|
|
|
timeout: parseInt(document.getElementById('configTimeout').value),
|
|
|
|
|
|
maxRetries: parseInt(document.getElementById('configMaxRetries').value),
|
|
|
|
|
|
temperature: parseFloat(document.getElementById('configTemperature').value),
|
|
|
|
|
|
topP: parseFloat(document.getElementById('configTopP').value),
|
|
|
|
|
|
topK: parseInt(document.getElementById('configTopK').value),
|
|
|
|
|
|
maxTokens: parseInt(document.getElementById('configMaxTokens').value),
|
|
|
|
|
|
systemPrompt: document.getElementById('configSystemPrompt').value,
|
|
|
|
|
|
streaming: document.getElementById('configStreaming').checked,
|
|
|
|
|
|
functions: document.getElementById('configFunctions').checked,
|
|
|
|
|
|
logRequests: document.getElementById('configLogRequests').checked
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/${currentModel.id}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(modelData)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前模型引用
|
|
|
|
|
|
const updatedModel = modelConfigs.find(m => m.id === currentModel.id);
|
|
|
|
|
|
if (updatedModel) {
|
|
|
|
|
|
currentModel = updatedModel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
alert('模型配置已保存!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('保存失败: ' + data.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('保存模型失败:', error);
|
|
|
|
|
|
alert('保存模型失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 测试模型连接
|
|
|
|
|
|
async function testModelConnection() {
|
|
|
|
|
|
if (!currentModel) {
|
|
|
|
|
|
alert('请先选择一个模型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const testBtn = document.getElementById('testBtn');
|
|
|
|
|
|
testBtn.disabled = true;
|
|
|
|
|
|
testBtn.innerHTML = '<svg class="w-4 h-4 inline mr-1 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>测试中...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/${currentModel.id}/test`, {
|
|
|
|
|
|
method: 'POST'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
currentModel.status = data.data.status;
|
|
|
|
|
|
document.getElementById('modelStatus').textContent = data.data.status;
|
|
|
|
|
|
document.getElementById('modelStatus').className = `px-2 py-1 text-xs rounded ${getModelStatusClass(data.data.status)} mr-2`;
|
|
|
|
|
|
alert(data.data.message);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('测试失败: ' + data.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新加载列表以更新状态
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('测试连接失败:', error);
|
|
|
|
|
|
alert('测试连接失败: 网络错误');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
testBtn.disabled = false;
|
|
|
|
|
|
testBtn.innerHTML = '<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>测试连接';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除模型
|
|
|
|
|
|
async function deleteModel() {
|
|
|
|
|
|
if (!currentModel) {
|
|
|
|
|
|
alert('请先选择一个模型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const confirmMessage = `确定要删除模型 "${currentModel.name}" 吗?\n\n此操作不可撤销!`;
|
|
|
|
|
|
|
|
|
|
|
|
if (confirm(confirmMessage)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/${currentModel.id}`, {
|
|
|
|
|
|
method: 'DELETE'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
// 重置当前模型
|
|
|
|
|
|
currentModel = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 重新加载列表
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
|
|
|
|
|
|
// 显示默认视图
|
|
|
|
|
|
document.getElementById('config-default').classList.remove('hidden');
|
|
|
|
|
|
document.getElementById('config-form').classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
alert('模型删除成功!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('删除失败: ' + data.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除模型失败:', error);
|
|
|
|
|
|
alert('删除模型失败: 网络错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤模型
|
|
|
|
|
|
function filterModels() {
|
|
|
|
|
|
const searchTerm = document.getElementById('modelSearch').value.toLowerCase();
|
|
|
|
|
|
const filteredModels = modelConfigs.filter(model =>
|
|
|
|
|
|
model.name.toLowerCase().includes(searchTerm) ||
|
|
|
|
|
|
model.type.toLowerCase().includes(searchTerm) ||
|
|
|
|
|
|
model.provider.toLowerCase().includes(searchTerm)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 临时替换模型列表
|
|
|
|
|
|
const originalModels = modelConfigs;
|
|
|
|
|
|
modelConfigs = filteredModels;
|
|
|
|
|
|
loadModelList();
|
|
|
|
|
|
modelConfigs = originalModels;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
// 初始化模型配置(如果当前在配置页面)
|
|
|
|
|
|
const currentPath = window.location.pathname;
|
|
|
|
|
|
if (currentPath.includes('main.html')) {
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
const configPage = document.getElementById('config-page');
|
|
|
|
|
|
if (configPage && !configPage.classList.contains('hidden')) {
|
|
|
|
|
|
await initModelConfig();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 模型对比相关功能 ============
|
|
|
|
|
|
|
|
|
|
|
|
let comparisonModels = {
|
|
|
|
|
|
modelA: null,
|
|
|
|
|
|
modelB: null,
|
|
|
|
|
|
modelC: null,
|
|
|
|
|
|
modelD: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化模型对比页面
|
|
|
|
|
|
async function initModelComparison() {
|
|
|
|
|
|
await loadModelsFromAPI();
|
|
|
|
|
|
loadComparisonModelList();
|
|
|
|
|
|
setupComparisonEventListeners();
|
|
|
|
|
|
|
|
|
|
|
|
// 重置页面状态 - 确保配置区展开
|
|
|
|
|
|
const configSection = document.getElementById('compare-config-section');
|
|
|
|
|
|
const showBtn = document.getElementById('showConfigBtn');
|
|
|
|
|
|
const toggleIcon = document.getElementById('toggleIcon');
|
|
|
|
|
|
const toggleText = document.getElementById('toggleConfigText');
|
|
|
|
|
|
|
|
|
|
|
|
if (configSection) {
|
|
|
|
|
|
configSection.style.display = 'block';
|
|
|
|
|
|
configSection.style.maxHeight = '1000px';
|
|
|
|
|
|
configSection.style.opacity = '1';
|
|
|
|
|
|
configSection.style.paddingTop = '1rem';
|
|
|
|
|
|
configSection.style.paddingBottom = '1rem';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (showBtn) {
|
|
|
|
|
|
showBtn.classList.add('hidden');
|
|
|
|
|
|
showBtn.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (toggleIcon) {
|
|
|
|
|
|
toggleIcon.setAttribute('d', 'M5 15l7-7 7 7');
|
|
|
|
|
|
toggleIcon.style.transform = 'rotate(0deg)';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (toggleText) {
|
|
|
|
|
|
toggleText.textContent = '折叠配置';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载模型对比列表
|
|
|
|
|
|
function loadComparisonModelList() {
|
|
|
|
|
|
const modelASelect = document.getElementById('modelASelect');
|
|
|
|
|
|
const modelBSelect = document.getElementById('modelBSelect');
|
|
|
|
|
|
const modelCSelect = document.getElementById('modelCSelect');
|
|
|
|
|
|
const modelDSelect = document.getElementById('modelDSelect');
|
|
|
|
|
|
|
|
|
|
|
|
if (!modelASelect || !modelBSelect) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 清空现有选项
|
|
|
|
|
|
modelASelect.innerHTML = '<option value="">请选择模型A</option>';
|
|
|
|
|
|
modelBSelect.innerHTML = '<option value="">请选择模型B</option>';
|
|
|
|
|
|
if (modelCSelect) modelCSelect.innerHTML = '<option value="">请选择模型C (可选)</option>';
|
|
|
|
|
|
if (modelDSelect) modelDSelect.innerHTML = '<option value="">请选择模型D (可选)</option>';
|
|
|
|
|
|
|
|
|
|
|
|
// 添加模型选项
|
|
|
|
|
|
modelConfigs.forEach((model, index) => {
|
|
|
|
|
|
const optionA = document.createElement('option');
|
|
|
|
|
|
optionA.value = index;
|
|
|
|
|
|
optionA.textContent = `${model.name} (${getProviderName(model.provider)})`;
|
|
|
|
|
|
modelASelect.appendChild(optionA);
|
|
|
|
|
|
|
|
|
|
|
|
const optionB = document.createElement('option');
|
|
|
|
|
|
optionB.value = index;
|
|
|
|
|
|
optionB.textContent = `${model.name} (${getProviderName(model.provider)})`;
|
|
|
|
|
|
modelBSelect.appendChild(optionB);
|
|
|
|
|
|
|
|
|
|
|
|
if (modelCSelect) {
|
|
|
|
|
|
const optionC = document.createElement('option');
|
|
|
|
|
|
optionC.value = index;
|
|
|
|
|
|
optionC.textContent = `${model.name} (${getProviderName(model.provider)})`;
|
|
|
|
|
|
modelCSelect.appendChild(optionC);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (modelDSelect) {
|
|
|
|
|
|
const optionD = document.createElement('option');
|
|
|
|
|
|
optionD.value = index;
|
|
|
|
|
|
optionD.textContent = `${model.name} (${getProviderName(model.provider)})`;
|
|
|
|
|
|
modelDSelect.appendChild(optionD);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置事件监听器
|
|
|
|
|
|
function setupComparisonEventListeners() {
|
|
|
|
|
|
const modelASelect = document.getElementById('modelASelect');
|
|
|
|
|
|
const modelBSelect = document.getElementById('modelBSelect');
|
|
|
|
|
|
const modelCSelect = document.getElementById('modelCSelect');
|
|
|
|
|
|
const modelDSelect = document.getElementById('modelDSelect');
|
|
|
|
|
|
const testTemperature = document.getElementById('testTemperature');
|
|
|
|
|
|
|
|
|
|
|
|
if (modelASelect) {
|
|
|
|
|
|
modelASelect.onchange = () => handleModelSelection('A', modelASelect.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (modelBSelect) {
|
|
|
|
|
|
modelBSelect.onchange = () => handleModelSelection('B', modelBSelect.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (modelCSelect) {
|
|
|
|
|
|
modelCSelect.onchange = () => handleModelSelection('C', modelCSelect.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (modelDSelect) {
|
|
|
|
|
|
modelDSelect.onchange = () => handleModelSelection('D', modelDSelect.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (testTemperature) {
|
|
|
|
|
|
testTemperature.oninput = (e) => {
|
|
|
|
|
|
document.getElementById('tempDisplay').textContent = e.target.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理模型选择
|
|
|
|
|
|
function handleModelSelection(modelType, selectedIndex) {
|
|
|
|
|
|
const index = parseInt(selectedIndex);
|
|
|
|
|
|
if (isNaN(index) || index < 0 || index >= modelConfigs.length) {
|
|
|
|
|
|
comparisonModels[`model${modelType}`] = null;
|
|
|
|
|
|
updateModelInfo(modelType);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectedModel = modelConfigs[index];
|
|
|
|
|
|
comparisonModels[`model${modelType}`] = selectedModel;
|
|
|
|
|
|
updateModelInfo(modelType);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否可以选择
|
|
|
|
|
|
checkModelSelection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新模型信息显示
|
|
|
|
|
|
function updateModelInfo(modelType) {
|
|
|
|
|
|
const infoElement = document.getElementById(`model${modelType}Info`);
|
|
|
|
|
|
const model = comparisonModels[`model${modelType}`];
|
|
|
|
|
|
|
|
|
|
|
|
if (!infoElement) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!model) {
|
|
|
|
|
|
infoElement.innerHTML = '请先选择模型';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const statusClass = getModelStatusClass(model.status);
|
|
|
|
|
|
infoElement.innerHTML = `
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<div><strong>版本:</strong> ${model.version || '未设置'}</div>
|
|
|
|
|
|
<div><strong>类型:</strong> ${model.type || '未设置'}</div>
|
|
|
|
|
|
<div><strong>提供方:</strong> ${getProviderName(model.provider)}</div>
|
|
|
|
|
|
<div><strong>状态:</strong> <span class="px-2 py-1 text-xs rounded ${statusClass}">${model.status}</span></div>
|
|
|
|
|
|
<div><strong>Token限制:</strong> ${model.maxTokens || '未设置'}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查模型选择状态
|
|
|
|
|
|
function checkModelSelection() {
|
|
|
|
|
|
const modelASelect = document.getElementById('modelASelect');
|
|
|
|
|
|
const modelBSelect = document.getElementById('modelBSelect');
|
|
|
|
|
|
const modelCSelect = document.getElementById('modelCSelect');
|
|
|
|
|
|
const modelDSelect = document.getElementById('modelDSelect');
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有已选择的模型索引
|
|
|
|
|
|
const selected = [];
|
|
|
|
|
|
if (modelASelect && modelASelect.value) selected.push(modelASelect.value);
|
|
|
|
|
|
if (modelBSelect && modelBSelect.value) selected.push(modelBSelect.value);
|
|
|
|
|
|
if (modelCSelect && modelCSelect.value) selected.push(modelCSelect.value);
|
|
|
|
|
|
if (modelDSelect && modelDSelect.value) selected.push(modelDSelect.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个下拉框禁用已选择的模型
|
|
|
|
|
|
const selects = [modelASelect, modelBSelect, modelCSelect, modelDSelect];
|
|
|
|
|
|
selects.forEach(select => {
|
|
|
|
|
|
if (!select) return;
|
|
|
|
|
|
Array.from(select.options).forEach((option, index) => {
|
|
|
|
|
|
if (index === 0) return; // 跳过第一个选项
|
|
|
|
|
|
const optionValue = index.toString();
|
|
|
|
|
|
// 如果该选项在其他下拉框中被选中,则禁用
|
|
|
|
|
|
option.disabled = selected.includes(optionValue);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 折叠配置区
|
|
|
|
|
|
function collapseConfigSection() {
|
|
|
|
|
|
const configSection = document.getElementById('compare-config-section');
|
|
|
|
|
|
const toggleBtn = document.getElementById('toggleConfigBtn');
|
|
|
|
|
|
const showBtn = document.getElementById('showConfigBtn');
|
|
|
|
|
|
const toggleIcon = document.getElementById('toggleIcon');
|
|
|
|
|
|
const toggleText = document.getElementById('toggleConfigText');
|
|
|
|
|
|
|
|
|
|
|
|
// 添加过渡动画
|
|
|
|
|
|
configSection.style.transition = 'all 0.3s ease-in-out';
|
|
|
|
|
|
configSection.style.opacity = '0';
|
|
|
|
|
|
configSection.style.maxHeight = '0';
|
|
|
|
|
|
configSection.style.paddingTop = '0';
|
|
|
|
|
|
configSection.style.paddingBottom = '0';
|
|
|
|
|
|
|
|
|
|
|
|
// 图标旋转动画
|
|
|
|
|
|
toggleIcon.style.transition = 'transform 0.3s ease-in-out';
|
|
|
|
|
|
toggleIcon.style.transform = 'rotate(180deg)';
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
configSection.style.display = 'none';
|
|
|
|
|
|
showBtn.style.display = 'flex';
|
|
|
|
|
|
showBtn.style.opacity = '0';
|
|
|
|
|
|
showBtn.style.transform = 'translateY(-10px)';
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
|
showBtn.style.transition = 'all 0.3s ease-in-out';
|
|
|
|
|
|
showBtn.style.opacity = '1';
|
|
|
|
|
|
showBtn.style.transform = 'translateY(0)';
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新折叠按钮状态
|
|
|
|
|
|
toggleIcon.setAttribute('d', 'M19 9l-7 7-7-7');
|
|
|
|
|
|
toggleText.textContent = '显示配置';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 展开配置区
|
|
|
|
|
|
function expandConfigSection() {
|
|
|
|
|
|
const configSection = document.getElementById('compare-config-section');
|
|
|
|
|
|
const toggleBtn = document.getElementById('toggleConfigBtn');
|
|
|
|
|
|
const showBtn = document.getElementById('showConfigBtn');
|
|
|
|
|
|
const toggleIcon = document.getElementById('toggleIcon');
|
|
|
|
|
|
const toggleText = document.getElementById('toggleConfigText');
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏显示按钮
|
|
|
|
|
|
showBtn.style.transition = 'all 0.2s ease-in-out';
|
|
|
|
|
|
showBtn.style.opacity = '0';
|
|
|
|
|
|
showBtn.style.transform = 'translateY(-10px)';
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
showBtn.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
// 显示配置区
|
|
|
|
|
|
configSection.style.display = 'block';
|
|
|
|
|
|
configSection.style.maxHeight = '0';
|
|
|
|
|
|
configSection.style.opacity = '0';
|
|
|
|
|
|
configSection.style.paddingTop = '0';
|
|
|
|
|
|
configSection.style.paddingBottom = '0';
|
|
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
|
configSection.style.transition = 'all 0.3s ease-in-out';
|
|
|
|
|
|
configSection.style.opacity = '1';
|
|
|
|
|
|
configSection.style.maxHeight = '1000px';
|
|
|
|
|
|
configSection.style.paddingTop = '1rem';
|
|
|
|
|
|
configSection.style.paddingBottom = '1rem';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 图标旋转动画
|
|
|
|
|
|
toggleIcon.style.transition = 'transform 0.3s ease-in-out';
|
|
|
|
|
|
toggleIcon.style.transform = 'rotate(0deg)';
|
|
|
|
|
|
}, 200);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新展开按钮状态
|
|
|
|
|
|
toggleIcon.setAttribute('d', 'M5 15l7-7 7 7');
|
|
|
|
|
|
toggleText.textContent = '折叠配置';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换配置区显示/隐藏
|
|
|
|
|
|
function toggleConfigSection() {
|
|
|
|
|
|
const configSection = document.getElementById('compare-config-section');
|
|
|
|
|
|
const showBtn = document.getElementById('showConfigBtn');
|
|
|
|
|
|
const isHidden = configSection.style.display === 'none' || configSection.style.maxHeight === '0px' || configSection.style.maxHeight === '0';
|
|
|
|
|
|
|
|
|
|
|
|
if (isHidden || showBtn.style.display !== 'none') {
|
|
|
|
|
|
expandConfigSection();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
collapseConfigSection();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示配置区
|
|
|
|
|
|
function showConfigSection() {
|
|
|
|
|
|
expandConfigSection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 运行模型对比
|
|
|
|
|
|
async function runModelComparison() {
|
|
|
|
|
|
const prompt = document.getElementById('testPrompt').value.trim();
|
|
|
|
|
|
const temperature = parseFloat(document.getElementById('testTemperature').value);
|
|
|
|
|
|
const maxTokens = parseInt(document.getElementById('testMaxTokens').value);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有选择的模型
|
|
|
|
|
|
const selectedModels = [];
|
|
|
|
|
|
for (let modelType of ['A', 'B', 'C', 'D']) {
|
|
|
|
|
|
const model = comparisonModels[`model${modelType}`];
|
|
|
|
|
|
if (model) {
|
|
|
|
|
|
selectedModels.push({ model, modelType });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证输入
|
|
|
|
|
|
if (selectedModels.length < 2) {
|
|
|
|
|
|
alert('请至少选择两个模型进行对比');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!prompt) {
|
|
|
|
|
|
alert('请输入测试提示词');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示结果区域
|
|
|
|
|
|
document.getElementById('comparisonDefault').classList.add('hidden');
|
|
|
|
|
|
document.getElementById('comparisonResults').classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
// 折叠配置区
|
|
|
|
|
|
collapseConfigSection();
|
|
|
|
|
|
|
|
|
|
|
|
// 重置输出区域
|
|
|
|
|
|
resetOutputAreas();
|
|
|
|
|
|
|
|
|
|
|
|
// 显示已选择模型的流式输出状态,并显示/隐藏对应的卡片
|
|
|
|
|
|
selectedModels.forEach(({ model, modelType }) => {
|
|
|
|
|
|
// 更新模型名称
|
|
|
|
|
|
const nameElement = document.getElementById(`model${modelType}Name`);
|
|
|
|
|
|
if (nameElement) {
|
|
|
|
|
|
nameElement.textContent = model.name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示对应的输出卡片
|
|
|
|
|
|
const outputCard = document.getElementById(`model${modelType}OutputCard`);
|
|
|
|
|
|
if (outputCard) {
|
|
|
|
|
|
outputCard.classList.remove('hidden');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示流式输出状态
|
|
|
|
|
|
const outputElement = document.getElementById(`model${modelType}Output`);
|
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
|
outputElement.innerHTML = '<div class="text-dashboard-text"><span class="animate-pulse">正在生成中...</span></div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏未选择模型的输出卡片
|
|
|
|
|
|
for (let modelType of ['C', 'D']) {
|
|
|
|
|
|
const model = comparisonModels[`model${modelType}`];
|
|
|
|
|
|
const outputCard = document.getElementById(`model${modelType}OutputCard`);
|
|
|
|
|
|
if (outputCard && !model) {
|
|
|
|
|
|
outputCard.classList.add('hidden');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开始对比
|
|
|
|
|
|
const compareBtn = document.getElementById('compareBtn');
|
|
|
|
|
|
compareBtn.disabled = true;
|
|
|
|
|
|
compareBtn.innerHTML = '<svg class="w-5 h-5 inline mr-2 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>对比中...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 创建流式显示回调
|
|
|
|
|
|
const streamingCache = {};
|
|
|
|
|
|
const onChunkCallbacks = {};
|
|
|
|
|
|
const modelPromises = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个选择的模型创建回调和Promise
|
|
|
|
|
|
selectedModels.forEach(({ model, modelType }) => {
|
|
|
|
|
|
streamingCache[modelType] = { output: '', responseTime: 0, streaming: false };
|
|
|
|
|
|
|
|
|
|
|
|
onChunkCallbacks[modelType] = (chunk) => {
|
|
|
|
|
|
streamingCache[modelType].output = chunk.content;
|
|
|
|
|
|
streamingCache[modelType].responseTime = Date.now();
|
|
|
|
|
|
streamingCache[modelType].streaming = true;
|
|
|
|
|
|
displayStreamingModelResult(modelType, streamingCache[modelType]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 创建模型调用Promise
|
|
|
|
|
|
const promise = withTimeout(
|
|
|
|
|
|
callRealModel(model, prompt, temperature, maxTokens, model.streaming ? onChunkCallbacks[modelType] : null),
|
|
|
|
|
|
60000
|
|
|
|
|
|
);
|
|
|
|
|
|
modelPromises.push({ promise, modelType });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 并行运行所有模型
|
|
|
|
|
|
const results = await Promise.allSettled(modelPromises.map(item => item.promise));
|
|
|
|
|
|
|
|
|
|
|
|
// 处理所有模型结果
|
|
|
|
|
|
results.forEach((result, index) => {
|
|
|
|
|
|
const modelType = modelPromises[index].modelType;
|
|
|
|
|
|
|
|
|
|
|
|
if (result.status === 'fulfilled') {
|
|
|
|
|
|
if (!result.value.streaming) {
|
|
|
|
|
|
displayModelResult(modelType, result.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
displayModelError(modelType, result.reason);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('对比过程中出错:', error);
|
|
|
|
|
|
alert('对比过程中出错,请重试');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
compareBtn.disabled = false;
|
|
|
|
|
|
compareBtn.innerHTML = '<svg class="w-5 h-5 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>开始对比';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Promise超时控制工具函数
|
|
|
|
|
|
function withTimeout(promise, timeoutMs) {
|
|
|
|
|
|
return Promise.race([
|
|
|
|
|
|
promise,
|
|
|
|
|
|
new Promise((_, reject) =>
|
|
|
|
|
|
setTimeout(() => reject(new Error(`请求超时 (${timeoutMs / 1000}秒)`)), timeoutMs)
|
|
|
|
|
|
)
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用真实模型API(支持流式输出)
|
|
|
|
|
|
async function callRealModel(model, prompt, temperature, maxTokens, onChunk) {
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/models/${model.id}/call`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
prompt: prompt,
|
|
|
|
|
|
stream: model.streaming || false
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error(`HTTP ${response.status}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否启用流式输出
|
|
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
|
|
|
|
const isStreaming = contentType?.includes('text/plain');
|
|
|
|
|
|
console.log('[流式调试] Content-Type:', contentType);
|
|
|
|
|
|
console.log('[流式调试] isStreaming:', isStreaming);
|
|
|
|
|
|
console.log('[流式调试] onChunk:', onChunk);
|
|
|
|
|
|
|
|
|
|
|
|
if (isStreaming) {
|
|
|
|
|
|
// 流式处理
|
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
let fullContent = '';
|
|
|
|
|
|
let chunkCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
|
|
if (done) break;
|
|
|
|
|
|
|
|
|
|
|
|
const chunk = decoder.decode(value);
|
|
|
|
|
|
console.log('[流式调试] 收到数据块:', chunk);
|
|
|
|
|
|
const lines = chunk.split('\n');
|
|
|
|
|
|
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
|
if (line.startsWith('data: ')) {
|
|
|
|
|
|
const data = line.slice(6);
|
|
|
|
|
|
if (data === '') continue;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
console.log('[流式调试] 解析数据:', parsed);
|
|
|
|
|
|
if (parsed.content !== undefined) {
|
|
|
|
|
|
if (parsed.content) {
|
|
|
|
|
|
fullContent += parsed.content;
|
|
|
|
|
|
chunkCount++;
|
|
|
|
|
|
// 调用回调函数更新UI
|
|
|
|
|
|
if (onChunk) {
|
|
|
|
|
|
console.log('[流式调试] 调用回调函数, 当前内容长度:', fullContent.length);
|
|
|
|
|
|
onChunk({
|
|
|
|
|
|
content: fullContent,
|
|
|
|
|
|
delta: parsed.content,
|
|
|
|
|
|
done: parsed.done
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (parsed.done) {
|
|
|
|
|
|
console.log('[流式调试] 流式输出完成, 总块数:', chunkCount);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[流式调试] 解析错误:', e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const actualResponseTime = Date.now() - startTime;
|
|
|
|
|
|
return {
|
|
|
|
|
|
output: fullContent,
|
|
|
|
|
|
responseTime: actualResponseTime,
|
|
|
|
|
|
tokenCount: 0, // 流式模式下可能无法获取准确计数
|
|
|
|
|
|
modelName: model.name,
|
|
|
|
|
|
streaming: true
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 非流式处理
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
const actualResponseTime = Date.now() - startTime;
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success && data.data.success) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
output: data.data.content,
|
|
|
|
|
|
responseTime: actualResponseTime,
|
|
|
|
|
|
tokenCount: data.data.usage?.total_tokens || 0,
|
|
|
|
|
|
modelName: data.data.model,
|
|
|
|
|
|
streaming: false
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(data.data?.error || '模型调用失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 如果是超时错误,直接抛出
|
|
|
|
|
|
if (error.message.includes('超时')) {
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 其他错误包装
|
|
|
|
|
|
throw new Error(`API调用错误: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟模型响应(备用方案)
|
|
|
|
|
|
function simulateModelResponse(model, prompt, temperature, maxTokens) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
const responseTime = Math.random() * 3000 + 1000; // 1-4秒随机响应时间
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 模拟响应
|
|
|
|
|
|
const responses = [
|
|
|
|
|
|
`这是来自${model.name}的回答。对于您的问题"${prompt}",我提供了详细的分析和见解。\n\n根据我的理解,这个问题涉及多个方面。首先,我们需要考虑根本原因和可能的解决方案。\n\n我的建议是:\n1. 深入分析问题的核心\n2. 评估所有可行的选择\n3. 制定具体的行动计划\n\n希望这个回答对您有帮助!`,
|
|
|
|
|
|
`${model.name}的回应:\n\n感谢您的提问。我将尝试从不同角度来回答您的问题。\n\n关键要点:\n• 第一点是关于基础概念的理解\n• 第二点涉及实际应用场景\n• 第三点需要进一步考虑\n\n详细说明:这是一个复杂的问题,需要综合考虑多个因素。基于我的训练数据和推理能力,我认为最佳的方法是采用系统性的分析方法。`,
|
|
|
|
|
|
`针对"${prompt}"这个问题,${model.name}给出以下回答:\n\n从技术角度来看,这个问题有几个重要的方面需要关注。首先,我们需要理解基本原理,然后应用到实际情境中。\n\n建议的解决方案:\n1. 明确目标和期望结果\n2. 评估现有资源和限制\n3. 逐步实施改进措施\n\n总结:通过系统化的方法,我们可以有效地解决这个挑战。`
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
|
|
|
|
|
|
const tokenCount = Math.floor(randomResponse.length / 4); // 估算token数
|
|
|
|
|
|
const actualResponseTime = Date.now() - startTime;
|
|
|
|
|
|
|
|
|
|
|
|
resolve({
|
|
|
|
|
|
output: randomResponse,
|
|
|
|
|
|
responseTime: actualResponseTime,
|
|
|
|
|
|
tokenCount: tokenCount
|
|
|
|
|
|
});
|
|
|
|
|
|
}, responseTime);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示模型结果
|
|
|
|
|
|
function displayModelResult(modelType, result) {
|
|
|
|
|
|
const outputElement = document.getElementById(`model${modelType}Output`);
|
|
|
|
|
|
const timeElement = document.getElementById(`model${modelType}Time`);
|
|
|
|
|
|
const tokensElement = document.getElementById(`model${modelType}Tokens`);
|
|
|
|
|
|
|
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
|
outputElement.innerHTML = `<div class="whitespace-pre-wrap text-dashboard-text">${result.output}</div>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timeElement) {
|
|
|
|
|
|
timeElement.textContent = `${result.responseTime}ms`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (tokensElement) {
|
|
|
|
|
|
tokensElement.textContent = result.tokenCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示流式模型结果
|
|
|
|
|
|
function displayStreamingModelResult(modelType, result) {
|
|
|
|
|
|
const outputElement = document.getElementById(`model${modelType}Output`);
|
|
|
|
|
|
const timeElement = document.getElementById(`model${modelType}Time`);
|
|
|
|
|
|
const tokensElement = document.getElementById(`model${modelType}Tokens`);
|
|
|
|
|
|
|
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
|
outputElement.innerHTML = `<div class="whitespace-pre-wrap text-dashboard-text">${result.output}</div>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timeElement) {
|
|
|
|
|
|
timeElement.textContent = `${result.responseTime}ms`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (tokensElement) {
|
|
|
|
|
|
tokensElement.textContent = result.tokenCount || '流式输出';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示模型错误
|
|
|
|
|
|
function displayModelError(modelType, error) {
|
|
|
|
|
|
const outputElement = document.getElementById(`model${modelType}Output`);
|
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
|
outputElement.innerHTML = `<div class="text-red-500">错误: ${error.message || '请求失败'}</div>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置输出区域
|
|
|
|
|
|
function resetOutputAreas() {
|
|
|
|
|
|
['A', 'B'].forEach(modelType => {
|
|
|
|
|
|
const outputElement = document.getElementById(`model${modelType}Output`);
|
|
|
|
|
|
const timeElement = document.getElementById(`model${modelType}Time`);
|
|
|
|
|
|
const tokensElement = document.getElementById(`model${modelType}Tokens`);
|
|
|
|
|
|
|
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
|
outputElement.innerHTML = '<div class="text-dashboard-textLight">正在生成回答...</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timeElement) {
|
|
|
|
|
|
timeElement.textContent = '-';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (tokensElement) {
|
|
|
|
|
|
tokensElement.textContent = '-';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 复制到剪贴板
|
|
|
|
|
|
function copyToClipboard(elementId) {
|
|
|
|
|
|
const element = document.getElementById(elementId);
|
|
|
|
|
|
if (!element) return;
|
|
|
|
|
|
|
|
|
|
|
|
const text = element.textContent || element.innerText;
|
|
|
|
|
|
|
|
|
|
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
|
|
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
|
|
|
|
showToast('已复制到剪贴板');
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
fallbackCopyToClipboard(text);
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fallbackCopyToClipboard(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 备用复制方法
|
|
|
|
|
|
function fallbackCopyToClipboard(text) {
|
|
|
|
|
|
const textArea = document.createElement('textarea');
|
|
|
|
|
|
textArea.value = text;
|
|
|
|
|
|
textArea.style.position = 'fixed';
|
|
|
|
|
|
textArea.style.opacity = '0';
|
|
|
|
|
|
document.body.appendChild(textArea);
|
|
|
|
|
|
textArea.focus();
|
|
|
|
|
|
textArea.select();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
document.execCommand('copy');
|
|
|
|
|
|
showToast('已复制到剪贴板');
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('复制失败:', err);
|
|
|
|
|
|
alert('复制失败,请手动复制');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.body.removeChild(textArea);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示提示消息
|
|
|
|
|
|
function showToast(message) {
|
|
|
|
|
|
const toast = document.createElement('div');
|
|
|
|
|
|
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
|
|
|
|
|
|
toast.textContent = message;
|
|
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
toast.style.opacity = '0';
|
|
|
|
|
|
toast.style.transition = 'opacity 0.3s';
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
document.body.removeChild(toast);
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
2026-01-12 14:20:44 +08:00
|
|
|
|
</body>
|
2026-01-13 12:09:41 +08:00
|
|
|
|
</html>
|