Files
YG_FT_Platform/web/pages/model-compare-chat.html

696 lines
32 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>
<script src="../lib/tailwindcss/tailwind.js"></script>
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<style>
.sidebar-section-title {
padding: 0.5rem 1rem;
font-size: 0.75rem;
color: rgba(191, 203, 217, 0.7);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.nav-link:hover {
background-color: rgba(0, 21, 41, 0.2);
}
.form-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
transition: border-color 0.2s, outline 0.2s;
}
.form-input:focus {
border-color: #1890ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.25rem;
}
.bg-primary { background-color: #1890ff; }
.text-primary { color: #1890ff; }
.border-primary { border-color: #1890ff; }
:root { --primary: #1890ff; }
/* 滑块样式 */
.slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
border-radius: 3px;
background: #e5e7eb;
outline: none;
}
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary);
cursor: pointer;
box-shadow: 0 2px 6px rgba(24, 144, 255, 0.3);
transition: transform 0.2s;
}
.slider-thumb::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
/* 折叠动画 */
.input-section {
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
overflow: hidden;
}
.input-section.collapsed {
max-height: 0;
opacity: 0;
}
/* 流式输出光标 */
.typing-cursor::after {
content: '|';
animation: blink 1s infinite;
color: var(--primary);
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* 输出卡片动画 */
.output-card {
transition: all 0.3s ease;
}
.output-card.loading {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* 已选模型标签 */
.model-tag {
transition: all 0.2s;
}
.model-tag:hover {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
</style>
</head>
<body class="antialiased bg-gray-50 flex h-screen overflow-hidden">
<!-- 侧边导航 -->
<aside class="w-64 text-[#bfcbd9] flex-shrink-0 hidden md:block flex flex-col h-full" style="background-color: #001529;">
<!-- 平台LOGO区域 -->
<div class="p-4 border-b border-[#001529]/30 flex items-center">
<img src="../assets/logo/logo.png" alt="Logo" class="w-6 h-6 object-contain mr-2">
<span class="text-white font-medium">远光软件微调平台</span>
</div>
<!-- 导航主区域 -->
<nav class="flex-1 overflow-y-auto py-2">
<!-- 第一分区:模型服务 -->
<div class="sidebar-section-title">模型服务</div>
<a href="main.html" data-page="fine-tune" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-cogs w-5 text-center"></i>
<span class="ml-2">模型调优</span>
</a>
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">我的模型</span>
</a>
<a href="main.html?page=model-eval" data-page="model-eval" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-line-chart w-5 text-center"></i>
<span class="ml-2">模型评测</span>
</a>
<a href="main.html?page=model-compare" data-page="model-compare" class="nav-link active flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-server w-5 text-center"></i>
<span class="ml-2">模型对比</span>
</a>
<!-- 第二分区:资源管理 -->
<div class="sidebar-section-title mt-6">资源管理</div>
<a href="main.html?page=model-manage" data-page="model-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-cube w-5 text-center"></i>
<span class="ml-2">模型管理</span>
</a>
<a href="main.html?page=dataset-manage" data-page="dataset-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-file-text w-5 text-center"></i>
<span class="ml-2">数据集管理</span>
</a>
<a href="main.html?page=data-generate" data-page="data-generate" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">其他工具</span>
</a>
<!-- 第三分区:系统设置 -->
<div class="sidebar-section-title mt-6">系统设置</div>
<a href="main.html?page=config" data-page="config" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-bar-chart w-5 text-center"></i>
<span class="ml-2">平台性能</span>
</a>
</nav>
<!-- 底部信息区域 -->
<div class="p-4 border-t border-[#001529]/30 text-xs mt-auto">
<div class="mb-2 text-[#bfcbd9]/80">默认业务空间</div>
<div class="flex items-center justify-between">
<span class="text-[#bfcbd9]">版本 v1.0.0</span>
<i class="fa fa-question-circle-o text-[#bfcbd9]/70"></i>
</div>
</div>
</aside>
<!-- 主内容区 -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- 顶部导航 -->
<header class="bg-white border-b border-gray-200 shadow-sm">
<div class="flex items-center justify-between px-6 h-14">
<div class="flex items-center space-x-4">
<a href="main.html?page=model-compare" class="text-gray-500 hover:text-gray-700 flex items-center">
<i class="fa fa-arrow-left"></i>
<span class="ml-1">返回列表</span>
</a>
</div>
<div class="flex items-center space-x-4">
<div class="relative group">
<img src="https://picsum.photos/id/1005/32/32" class="w-8 h-8 rounded-full cursor-pointer" alt="用户头像">
<div class="absolute right-0 top-full mt-2 bg-white rounded shadow-lg py-1 hidden group-hover:block border border-gray-100 min-w-[140px]">
<a href="login.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 whitespace-nowrap">
<i class="fa fa-sign-out mr-1"></i>退出登录
</a>
</div>
</div>
</div>
</div>
</header></parameter>
<!-- 内容区域 -->
<main class="flex-1 overflow-y-auto bg-gray-100 p-6">
<!-- 输入区域 -->
<div id="inputSection" class="input-section bg-white rounded-lg shadow-sm mb-6">
<div class="p-6">
<!-- 面包屑 -->
<div class="flex items-center text-sm mb-6">
<a href="main.html?page=model-compare" class="text-primary hover:underline">模型对比</a>
<span class="mx-2 text-gray-300">/</span>
<span id="breadcrumbTask" class="text-gray-800 font-medium">对比任务</span>
</div>
<form id="chatForm">
<!-- 已配置模型展示 -->
<div class="mb-6">
<label class="form-label flex items-center mb-3">
<i class="fa fa-server text-primary mr-2"></i>
已配置模型
<span class="ml-2 text-xs text-gray-400 font-normal">(共 <span id="modelCount">0</span> 个)</span>
</label>
<div id="selectedModelsList" class="flex flex-wrap gap-2">
<!-- 模型标签将通过JS渲染 -->
</div>
</div>
<!-- 系统提示词 -->
<div class="mb-5">
<label class="form-label flex items-center">
<i class="fa fa-cog text-primary mr-2"></i>
系统提示词
<span class="ml-2 text-xs text-gray-400 font-normal">(可选,设置模型行为)</span>
</label>
<textarea id="systemPrompt" class="form-input" rows="3"
placeholder="例如:你是一个专业的技术助手,善于回答各类问题。请用简洁清晰的语言回答。"></textarea>
</div>
<!-- 用户提问 -->
<div class="mb-5">
<label class="form-label flex items-center">
<i class="fa fa-comment text-primary mr-2"></i>
用户提问
<span class="text-red-500 ml-1">*</span>
</label>
<textarea id="userQuestion" class="form-input" rows="4"
placeholder="请输入您想要对比的问题..."></textarea>
</div>
<!-- 推理参数设置 -->
<div class="mb-6">
<label class="form-label flex items-center mb-4">
<i class="fa fa-sliders text-primary mr-2"></i>
推理参数设置
</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<!-- Temperature -->
<div>
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-gray-600">Temperature</span>
<span id="tempValue" class="text-sm text-primary font-medium">0.7</span>
</div>
<input type="range" id="temperature" class="slider-thumb" min="0" max="2" step="0.1" value="0.7">
<div class="flex justify-between text-xs text-gray-400 mt-1">
<span>保守</span>
<span>随机</span>
</div>
</div>
<!-- Top-p -->
<div>
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-gray-600">Top-p</span>
<span id="topPValue" class="text-sm text-primary font-medium">0.9</span>
</div>
<input type="range" id="topP" class="slider-thumb" min="0" max="1" step="0.05" value="0.9">
<div class="flex justify-between text-xs text-gray-400 mt-1">
<span>集中</span>
<span>广泛</span>
</div>
</div>
<!-- Top-k -->
<div>
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-gray-600">Top-k</span>
<span id="topKValue" class="text-sm text-primary font-medium">50</span>
</div>
<input type="range" id="topK" class="slider-thumb" min="1" max="100" step="1" value="50">
<div class="flex justify-between text-xs text-gray-400 mt-1">
<span>精确</span>
<span>多样</span>
</div>
</div>
<!-- Max Tokens -->
<div>
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-gray-600">Max Tokens</span>
<span id="maxTokensValue" class="text-sm text-primary font-medium">2048</span>
</div>
<input type="range" id="maxTokens" class="slider-thumb" min="256" max="4096" step="256" value="2048">
<div class="flex justify-between text-xs text-gray-400 mt-1">
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<!-- 按钮区域 -->
<div class="flex items-center justify-end pt-4 border-t border-gray-100">
<div class="flex items-center space-x-3">
<button type="button" onclick="goBack()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">
取消
</button>
<button type="button" id="startBtn" class="px-6 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90 transition-colors flex items-center disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa fa-play mr-2"></i>
开始提问
</button>
</div>
</div>
</form>
</div>
</div>
<!-- 输出区域 -->
<div id="outputSection" class="hidden">
<!-- 重新提问按钮 -->
<div class="flex items-center justify-end mb-4">
<button id="resetBtn" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90 transition-colors flex items-center">
<i class="fa fa-refresh mr-2"></i>
重新提问
</button>
</div>
<!-- 模型输出网格2x2 -->
<div id="outputGrid" class="grid grid-cols-2 gap-4">
<!-- 输出卡片将通过JS动态生成 -->
</div>
</div>
</main>
</div>
<script>
// 动态获取 API 基础地址
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:8080/api`;
};
const API_BASE = getApiBase();
// 模拟模型数据(用于演示)
const mockModels = [
{ id: 1, name: 'GPT-4', type: 'LLM', description: 'OpenAI GPT-4 大语言模型' },
{ id: 2, name: 'Claude-3', type: 'LLM', description: 'Anthropic Claude-3 模型' },
{ id: 3, name: '文心一言', type: 'LLM', description: '百度文心一言大模型' },
{ id: 4, name: '通义千问', type: 'LLM', description: '阿里通义千问大模型' },
{ id: 5, name: 'ChatGLM', type: 'LLM', description: '智谱ChatGLM对话模型' },
{ id: 6, name: '星火认知', type: 'LLM', description: '讯飞星火认知大模型' }
];
let allModels = [];
let selectedModelIds = [];
let isGenerating = false;
let currentStreamingIntervals = [];
let compareTaskId = null;
let compareTaskData = null;
// 模拟回复内容
const mockResponses = [
"这是一个基于深度学习的自然语言处理模型回答。我可以处理各种复杂的问题,包括代码编写、文本分析、知识问答等多种任务。我的训练数据涵盖了广泛的领域知识,能够提供准确和有用的信息。",
"您好!我是一个人工智能语言模型,很高兴为您服务。我可以帮助您解答问题、提供建议、进行创意写作等。如果您有任何需要,请随时告诉我,我会尽力提供帮助。",
"这是一个很有趣的问题!从技术角度来看,我们需要考虑多个因素:数据质量、模型架构、训练策略等。深度学习在近年来取得了巨大进展,但仍然存在一些挑战需要解决。",
"根据我的理解,这个问题涉及到以下几个方面:首先,需要明确问题的具体背景;其次,要分析相关的技术方案;最后,需要评估实施的成本和收益。建议您先收集更多信息再做决定。"
];
// 页面初始化
document.addEventListener('DOMContentLoaded', async function() {
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search);
compareTaskId = urlParams.get('id');
// 加载数据
await Promise.all([
loadCompareTask(),
loadModels()
]);
bindEvents();
});
// 加载对比任务数据
async function loadCompareTask() {
if (!compareTaskId) {
// 没有任务ID无法进行对比
updatePageTitle('请选择对比任务');
document.getElementById('startBtn').disabled = true;
showToast('请从列表页进入对比任务', 'warning');
return;
}
try {
const response = await fetch(`${API_BASE}/model-compare/${compareTaskId}`);
const result = await response.json();
console.log('加载对比任务返回:', result);
if (result.code === 0) {
// result.data 可能是单个对象或数组
const taskData = Array.isArray(result.data)
? result.data.find(item => item && item.id == compareTaskId)
: result.data;
if (taskData) {
compareTaskData = taskData;
updatePageTitle(taskData.model_name || '模型对比');
// 获取任务中的模型列表
const modelsField = taskData.models;
console.log('models字段:', modelsField, typeof modelsField);
if (modelsField) {
try {
if (typeof modelsField === 'string') {
selectedModelIds = JSON.parse(modelsField);
} else if (Array.isArray(modelsField)) {
selectedModelIds = modelsField;
}
console.log('解析后的模型ID:', selectedModelIds);
renderSelectedModels();
} catch (e) {
console.error('解析模型列表失败:', e);
}
}
} else {
console.warn('未找到对应任务数据');
}
} else {
showToast('加载对比任务失败: ' + result.message, 'error');
}
} catch (error) {
console.error('加载对比任务失败:', error);
showToast('加载对比任务失败', 'error');
}
}
// 更新页面标题
function updatePageTitle(title) {
document.getElementById('breadcrumbTask').textContent = title;
}
// 加载模型列表
async function loadModels() {
try {
const response = await fetch(`${API_BASE}/model-manage`);
const result = await response.json();
if (result.code === 0) {
allModels = result.data || [];
}
} catch (error) {
console.error('加载模型失败:', error);
allModels = mockModels;
}
}
// 渲染已选模型标签
function renderSelectedModels() {
const container = document.getElementById('selectedModelsList');
const modelCount = document.getElementById('modelCount');
const allAvailableModels = [...allModels, ...mockModels];
modelCount.textContent = selectedModelIds.length;
if (selectedModelIds.length === 0) {
container.innerHTML = '<p class="text-gray-400 text-sm">暂无配置模型</p>';
return;
}
container.innerHTML = selectedModelIds.map((modelId, index) => {
const model = allAvailableModels.find(m => m.id === modelId);
const colors = ['bg-blue-100 text-blue-700 border-blue-200', 'bg-green-100 text-green-700 border-green-200', 'bg-purple-100 text-purple-700 border-purple-200', 'bg-orange-100 text-orange-700 border-orange-200'];
const colorClass = colors[index % colors.length];
return `
<span class="model-tag px-3 py-1.5 rounded-lg text-sm font-medium flex items-center ${colorClass}">
${model?.name || `模型 ${modelId}`}
</span>
`;
}).join('');
}
// 绑定事件
function bindEvents() {
// 滑块值更新
const sliders = [
{ id: 'temperature', valueId: 'tempValue' },
{ id: 'topP', valueId: 'topPValue' },
{ id: 'topK', valueId: 'topKValue' },
{ id: 'maxTokens', valueId: 'maxTokensValue' }
];
sliders.forEach(({ id, valueId }) => {
const slider = document.getElementById(id);
const valueDisplay = document.getElementById(valueId);
if (slider && valueDisplay) {
slider.addEventListener('input', function() {
valueDisplay.textContent = this.value;
});
}
});
// 开始提问按钮
document.getElementById('startBtn').addEventListener('click', startGeneration);
// 重新提问按钮
document.getElementById('resetBtn').addEventListener('click', resetChat);
}
// 折叠/展开输入区域
function toggleInputSection() {
const inputSection = document.getElementById('inputSection');
const outputSection = document.getElementById('outputSection');
inputSection.classList.toggle('collapsed');
outputSection.classList.toggle('hidden');
}
// 开始生成回答
async function startGeneration() {
const userQuestion = document.getElementById('userQuestion').value.trim();
// 验证
if (!userQuestion) {
showToast('请输入用户提问', 'warning');
return;
}
if (selectedModelIds.length === 0) {
showToast('该任务没有配置模型', 'warning');
return;
}
// 禁用按钮
isGenerating = true;
document.getElementById('startBtn').disabled = true;
document.getElementById('resetBtn').disabled = true;
document.getElementById('resetBtn').classList.add('opacity-50', 'cursor-not-allowed');
// 折叠输入区域,显示输出区域
document.getElementById('inputSection').classList.add('collapsed');
document.getElementById('outputSection').classList.remove('hidden');
// 初始化输出卡片
initializeOutputCards();
// 开始流式输出
await simulateStreaming();
}
// 初始化输出卡片
function initializeOutputCards() {
const grid = document.getElementById('outputGrid');
const allAvailableModels = [...allModels, ...mockModels];
grid.innerHTML = selectedModelIds.map((modelId, index) => {
const model = allAvailableModels.find(m => m.id === modelId) || { name: `模型 ${modelId}` };
const colors = ['bg-blue-100 text-blue-700', 'bg-green-100 text-green-700', 'bg-purple-100 text-purple-700', 'bg-orange-100 text-orange-700'];
const colorClass = colors[index % colors.length];
return `
<div class="output-card bg-white rounded-lg shadow-sm p-4 h-96 flex flex-col" id="output-${modelId}">
<div class="flex items-center justify-between pb-3 border-b border-gray-100">
<div class="flex items-center">
<span class="px-2.5 py-1 rounded text-sm font-medium ${colorClass}">${model.name}</span>
</div>
<span id="status-${modelId}" class="text-xs text-gray-400 flex items-center">
<i class="fa fa-clock-o mr-1"></i>
等待中
</span>
</div>
<div id="content-${modelId}" class="flex-1 overflow-y-auto mt-3 text-sm text-gray-600 leading-relaxed typing-cursor">
<span class="text-gray-300">模型即将开始生成回答...</span>
</div>
</div>
`;
}).join('');
}
// 模拟流式输出
async function simulateStreaming() {
// 清除之前的定时器
currentStreamingIntervals.forEach(interval => clearInterval(interval));
currentStreamingIntervals = [];
// 延迟启动不同模型的输出
for (let i = 0; i < selectedModelIds.length; i++) {
const modelId = selectedModelIds[i];
await delay(500); // 每个模型延迟启动
streamModelResponse(modelId, i);
}
}
// 流式输出单个模型
function streamModelResponse(modelId, responseIndex) {
const contentEl = document.getElementById(`content-${modelId}`);
const statusEl = document.getElementById(`status-${modelId}`);
if (!contentEl || !statusEl) return;
statusEl.innerHTML = '<i class="fa fa-spinner fa-spin mr-1"></i> 生成中...';
statusEl.classList.remove('text-gray-400');
statusEl.classList.add('text-primary');
// 清空内容
contentEl.innerHTML = '';
contentEl.classList.remove('typing-cursor');
// 获取模拟回复
const response = mockResponses[responseIndex % mockResponses.length];
let charIndex = 0;
// 模拟打字效果
const interval = setInterval(() => {
if (charIndex < response.length) {
contentEl.textContent = response.substring(0, charIndex + 1);
charIndex++;
// 滚动到底部
contentEl.scrollTop = contentEl.scrollHeight;
} else {
clearInterval(interval);
// 从数组中移除
const idx = currentStreamingIntervals.indexOf(interval);
if (idx > -1) currentStreamingIntervals.splice(idx, 1);
// 完成
statusEl.innerHTML = '<i class="fa fa-check-circle text-green-500 mr-1"></i> 完成';
statusEl.classList.remove('text-primary');
statusEl.classList.add('text-green-500');
contentEl.classList.add('typing-cursor');
// 恢复按钮状态(所有模型都完成后)
if (currentStreamingIntervals.length === 0) {
isGenerating = false;
document.getElementById('startBtn').disabled = false;
document.getElementById('resetBtn').disabled = false;
document.getElementById('resetBtn').classList.remove('opacity-50', 'cursor-not-allowed');
}
}
}, 30 + Math.random() * 40); // 随机速度,更自然
currentStreamingIntervals.push(interval);
}
// 重置聊天
function resetChat() {
// 清除定时器
currentStreamingIntervals.forEach(interval => clearInterval(interval));
currentStreamingIntervals = [];
isGenerating = false;
document.getElementById('startBtn').disabled = false;
document.getElementById('resetBtn').disabled = false;
document.getElementById('resetBtn').classList.remove('opacity-50', 'cursor-not-allowed');
// 展开输入区域
document.getElementById('inputSection').classList.remove('collapsed');
document.getElementById('outputSection').classList.add('hidden');
}
// 返回
function goBack() {
window.location.href = 'main.html?page=model-compare';
}
// 显示提示消息
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `fixed top-4 left-1/2 transform -translate-x-1/2 px-4 py-2 rounded-lg text-sm text-white z-50 transition-all duration-300 ${
type === 'warning' ? 'bg-yellow-500' :
type === 'error' ? 'bg-red-500' :
type === 'success' ? 'bg-green-500' : 'bg-primary'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translate(-50%, -20px)';
setTimeout(() => toast.remove(), 300);
}, 2000);
}
// 延迟函数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
</script>
</body>
</html>