Files
YG_FT_Platform/web/pages/model-inference.html

523 lines
23 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>
<script>
if (typeof console !== 'undefined' && console.warn) {
const originalWarn = console.warn;
console.warn = function(...args) {
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
return;
}
originalWarn.apply(console, args);
};
}
</script>
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<style>
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
.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); }
.sidebar-item-active {
background-color: rgba(24, 144, 255, 0.1);
color: #1890ff;
border-left: 4px solid #1890ff;
}
/* 侧边栏滑块动画 */
.sidebar-slider {
position: absolute;
width: 4px;
height: 0;
background-color: #1890ff;
border-radius: 0 2px 2px 0;
transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1),
height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
z-index: 10;
}
.nav-item-wrapper {
position: relative;
}
.nav-link {
position: relative;
z-index: 1;
}
.chat-message { animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.typing-indicator span {
display: inline-block;
width: 6px;
height: 6px;
background-color: #1890ff;
border-radius: 50%;
margin: 0 2px;
animation: typing 1.4s infinite ease-in-out;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-8px); } }
.bg-primary { background-color: #1890ff; }
.text-primary { color: #1890ff; }
.border-primary { border-color: #1890ff; }
</style>
</head>
<body class="antialiased bg-gray-100 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="pt-5 pb-3 border-b border-[#001529]/30 flex items-center justify-center pl-2">
<img src="../assets/logo/logo.png" alt="Logo" class="w-8 h-8 object-contain mr-2">
<span class="text-white font-medium text-base">远光软件微调平台</span>
</div>
<!-- 导航主区域 -->
<nav class="flex-1 overflow-y-auto py-2 relative">
<!-- 滑块指示器 -->
<div class="sidebar-slider" id="sidebar-slider"></div>
<!-- 第一分区:模型服务 -->
<div class="sidebar-section-title">模型服务</div>
<div class="nav-item-wrapper">
<a href="#" 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>
</div>
<div class="nav-item-wrapper">
<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>
</div>
<div class="nav-item-wrapper">
<a href="#" 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>
</div>
<div class="nav-item-wrapper">
<a href="#" data-page="model-compare" class="nav-link 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>
<!-- 第二分区:资源管理 -->
<div class="sidebar-section-title mt-6">资源管理</div>
<div class="nav-item-wrapper">
<a href="#" 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>
</div>
<div class="nav-item-wrapper">
<a href="#" 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>
</div>
<div class="nav-item-wrapper">
<a href="#" 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>
<!-- 第三分区:系统设置 -->
<div class="sidebar-section-title mt-6">系统设置</div>
<div class="nav-item-wrapper">
<a href="#" 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>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=logs" data-page="logs" 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>
</div>
</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>
</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=my-models" 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="flex items-center text-sm text-gray-600">
<i class="fa fa-cube text-primary mr-2"></i>
<span id="modelTitle">加载中...</span>
</div>
</div>
</div>
</header>
<!-- 对话区域 -->
<main class="flex-1 flex flex-col overflow-hidden bg-gray-50">
<!-- 模型信息栏 -->
<div class="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between">
<div class="flex items-center space-x-6">
<div class="flex items-center">
<span class="text-xs text-gray-400 mr-2">训练方法:</span>
<span id="trainMethod" class="px-2 py-1 bg-green-100 text-green-700 text-xs rounded">-</span>
</div>
<div class="flex items-center">
<span class="text-xs text-gray-400 mr-2">基座模型:</span>
<span id="baseModel" class="text-sm text-gray-700">-</span>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center">
<label class="text-xs text-gray-400 mr-2">Temperature:</label>
<input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7" class="w-24">
<span id="temperatureValue" class="ml-2 text-sm text-gray-600">0.7</span>
</div>
<button onclick="clearChat()" class="text-xs text-gray-500 hover:text-gray-700">
<i class="fa fa-trash-o mr-1"></i>清空对话
</button>
</div>
</div>
<!-- 聊天消息区域 -->
<div id="chatContainer" class="flex-1 overflow-y-auto p-6 space-y-4">
<!-- 欢迎消息 -->
<div class="chat-message flex justify-start">
<div class="max-w-3xl bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<div class="flex items-start">
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<i class="fa fa-robot text-white text-sm"></i>
</div>
<div class="ml-3">
<div class="text-xs text-gray-400 mb-1">AI 助手</div>
<div class="text-sm text-gray-700 leading-relaxed">
您好我是基于该模型训练的AI助手。请在下方输入您的问题我会尽力为您解答。
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="bg-white border-t border-gray-200 p-4">
<div class="max-w-4xl mx-auto">
<div class="flex items-stretch gap-3">
<div class="flex-1">
<textarea id="userInput" placeholder="请输入您的问题..." rows="2"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:border-primary focus:outline-none resize-none text-sm h-full"
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"></textarea>
</div>
<button onclick="sendMessage()" id="sendBtn"
class="px-5 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors flex items-center justify-center h-auto self-center">
<i class="fa fa-paper-plane mr-1.5"></i>
<span>发送</span>
</button>
</div>
<div class="mt-2 text-xs text-gray-400 flex items-center">
<i class="fa fa-info-circle mr-1"></i>
按 Enter 发送Shift+Enter 换行
</div>
</div>
</div>
</main>
</div>
<script>
// API 基础地址
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:7861/api`;
};
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
// 全局状态
let currentModel = {
name: '',
trainMethod: '',
baseModel: ''
};
let chatHistory = [];
let isLoading = false;
// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
// 解析URL参数
const urlParams = new URLSearchParams(window.location.search);
const modelName = urlParams.get('model');
const trainMethod = urlParams.get('method');
if (modelName) {
currentModel.name = decodeURIComponent(modelName);
document.getElementById('modelTitle').textContent = currentModel.name;
}
if (trainMethod) {
currentModel.trainMethod = decodeURIComponent(trainMethod);
const methodDisplay = {
'lora': 'LoRA',
'full': '全参微调',
'sft': 'SFT',
'dpo': 'DPO',
'cpt': 'CPT'
};
document.getElementById('trainMethod').textContent = methodDisplay[currentModel.trainMethod] || currentModel.trainMethod;
}
// 绑定侧边栏导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = this.dataset.page;
if (page === 'logs') {
window.location.href = `main.html?page=${page}`;
} else if (page) {
window.location.href = `main.html?page=${page}`;
}
});
if (link.dataset.page === 'my-models') {
link.classList.add('sidebar-item-active');
link.classList.remove('hover:bg-[#001529]/20');
}
});
updateSidebarSlider();
// 绑定温度滑块
const tempSlider = document.getElementById('temperature');
const tempValue = document.getElementById('temperatureValue');
tempSlider.addEventListener('input', function() {
tempValue.textContent = this.value;
});
// 聚焦输入框
document.getElementById('userInput').focus();
console.log('[DEBUG] 模型推理页面初始化:', currentModel);
});
// 发送消息
async function sendMessage() {
const input = document.getElementById('userInput');
const message = input.value.trim();
if (!message) return;
if (isLoading) return;
if (!currentModel.name) {
alert('请先选择模型');
return;
}
// 添加用户消息
addMessage('user', message);
chatHistory.push({ role: 'user', content: message });
// 清空输入框
input.value = '';
// 显示加载状态
isLoading = true;
updateSendButton(true);
// 添加AI加载提示
const loadingId = addLoadingMessage();
try {
const response = await fetch(`${API_BASE}/model-chat/trained`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model_name: currentModel.name,
train_method: currentModel.trainMethod || 'lora',
system_prompt: '',
user_question: message,
temperature: parseFloat(document.getElementById('temperature').value),
max_tokens: 2048
})
});
const result = await response.json();
// 移除加载提示
removeLoadingMessage(loadingId);
if (result.code === 0) {
const aiResponse = result.data?.response || '(无回复)';
addMessage('assistant', aiResponse);
chatHistory.push({ role: 'assistant', content: aiResponse });
} else {
addMessage('assistant', `推理失败: ${result.message || '未知错误'}`);
}
} catch (error) {
removeLoadingMessage(loadingId);
addMessage('assistant', `请求异常: ${error.message}`);
} finally {
isLoading = false;
updateSendButton(false);
}
}
// 添加消息到聊天框
function addMessage(role, content) {
const container = document.getElementById('chatContainer');
const isUser = role === 'user';
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message flex justify-' + (isUser ? 'end' : 'start');
const avatar = isUser
? `<div class="w-8 h-8 rounded-full bg-gray-400 flex items-center justify-center flex-shrink-0">
<i class="fa fa-user text-white text-sm"></i>
</div>`
: `<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<i class="fa fa-robot text-white text-sm"></i>
</div>`;
const bgClass = isUser ? 'bg-primary text-white' : 'bg-white border border-gray-200';
messageDiv.innerHTML = `
<div class="max-w-3xl ${bgClass} rounded-lg shadow-sm p-4">
<div class="flex items-start ${isUser ? 'flex-row-reverse' : ''}">
${avatar}
<div class="ml-3 ${isUser ? 'text-right' : ''} flex-1">
<div class="text-xs ${isUser ? 'text-gray-300' : 'text-gray-400'} mb-1">
${isUser ? '你' : 'AI 助手'}
</div>
<div class="text-sm leading-relaxed whitespace-pre-wrap">${escapeHtml(content)}</div>
</div>
</div>
</div>
`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
// 添加加载中的消息
function addLoadingMessage() {
const container = document.getElementById('chatContainer');
const loadingId = 'loading-' + Date.now();
const loadingDiv = document.createElement('div');
loadingDiv.id = loadingId;
loadingDiv.className = 'chat-message flex justify-start';
loadingDiv.innerHTML = `
<div class="max-w-3xl bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<div class="flex items-start">
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<i class="fa fa-robot text-white text-sm"></i>
</div>
<div class="ml-3">
<div class="text-xs text-gray-400 mb-1">AI 助手</div>
<div class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
</div>
</div>
`;
container.appendChild(loadingDiv);
container.scrollTop = container.scrollHeight;
return loadingId;
}
// 移除加载消息
function removeLoadingMessage(id) {
const loadingDiv = document.getElementById(id);
if (loadingDiv) {
loadingDiv.remove();
}
}
// 更新发送按钮状态
function updateSendButton(loading) {
const btn = document.getElementById('sendBtn');
if (loading) {
btn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>处理中';
btn.disabled = true;
btn.classList.add('opacity-50', 'cursor-not-allowed');
} else {
btn.innerHTML = '<i class="fa fa-paper-plane mr-2"></i>发送';
btn.disabled = false;
btn.classList.remove('opacity-50', 'cursor-not-allowed');
}
}
// 清空对话
function clearChat() {
const container = document.getElementById('chatContainer');
container.innerHTML = `
<div class="chat-message flex justify-start">
<div class="max-w-3xl bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<div class="flex items-start">
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
<i class="fa fa-robot text-white text-sm"></i>
</div>
<div class="ml-3">
<div class="text-xs text-gray-400 mb-1">AI 助手</div>
<div class="text-sm text-gray-700 leading-relaxed">
对话已清空,请继续提问。
</div>
</div>
</div>
</div>
</div>
`;
chatHistory = [];
}
// HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 更新侧边栏滑块位置
function updateSidebarSlider() {
const slider = document.getElementById('sidebar-slider');
if (!slider) return;
const activeLink = document.querySelector('.nav-link.bg-\\[\\#1890ff\\]\\/10');
if (activeLink) {
const wrapper = activeLink.closest('.nav-item-wrapper');
if (wrapper) {
slider.style.top = wrapper.offsetTop + 'px';
slider.style.height = wrapper.offsetHeight + 'px';
}
}
}
// 暴露到全局
window.sendMessage = sendMessage;
window.clearChat = clearChat;
</script>
</body>
</html>