Files
YG_FT_Platform/request/static/index.html

2718 lines
142 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大模型微调平台 - 主页</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配置匹配浅色主题 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
dashboard: {
bg: '#DBE0F9', // 主背景色与login一致
card: '#FFFFFF', // 卡片背景
primary: '#6065D9', // 主蓝色
secondary: '#764BA2', // 深蓝紫色
accent: '#17D7FA', // 浅蓝高亮
text: '#333333', // 文字色
textLight: '#666666' // 浅文字色
}
},
fontFamily: {
inter: ['Inter', 'sans-serif'],
},
animation: {
'pulse-slow': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
}
}
}
</script>
<!-- 网格布局样式 -->
<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;
}
}
@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;
}
}
@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; }
}
</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%;
}
}
</style>
<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>
</head>
<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 动态加载 -->
</div>
</div>
</div>
<!-- 数据预处理页面 - 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>
</div>
</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>
</div>
</div>
</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>
</div>
</div>
<!-- 默认状态 -->
<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>
</div>
<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>
</div>
<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>
</div>
</div>
<!-- 模型列表 -->
<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>
</div>
</div>
<!-- 模型配置表单 -->
<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>
</div>
</div>
</div>
</div>
</div>
</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>
</body>
</html>