1. 让AI修改了一些bug
This commit is contained in:
@@ -70,7 +70,10 @@
|
||||
"Bash(PYTHONPATH=src python -m uvicorn:*)",
|
||||
"Bash(ip route get 1.1.1.1)",
|
||||
"Bash(/d/Softwares/Anaconda/python:*)",
|
||||
"Bash(set PYTHONPATH=src)"
|
||||
"Bash(set PYTHONPATH=src)",
|
||||
"Bash(fuser:*)",
|
||||
"Read(//d/Code/Project/FT-Platform/**)",
|
||||
"Bash(xargs:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,9 +190,8 @@ def setup_routes(app: FastAPI):
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""根路径 - 重定向到前端监控界面"""
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url="/dashboard")
|
||||
"""根路径 - 返回主界面"""
|
||||
return FileResponse("../web/main.html")
|
||||
|
||||
@app.get("/dashboard")
|
||||
async def dashboard():
|
||||
@@ -307,12 +306,6 @@ def setup_routes(app: FastAPI):
|
||||
# 挂载静态文件目录
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
# 为前端页面创建特定路由
|
||||
@app.get("/dashboard")
|
||||
async def dashboard():
|
||||
"""前端监控界面"""
|
||||
return FileResponse("static/index.html")
|
||||
|
||||
# 只挂载静态资源(CSS, JS, 图片等)- 如果目录存在
|
||||
import os
|
||||
if os.path.exists("static"):
|
||||
|
||||
57
web/index.html
Normal file
57
web/index.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FT-Platform</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
p {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #5a67d8;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 FT-Platform</h1>
|
||||
<p>大模型微调平台</p>
|
||||
<a href="/pages/main.html" class="btn">进入平台</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
2683
web/main.html
Normal file
2683
web/main.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,22 @@
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.streaming-content {
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.streaming-cursor::after {
|
||||
content: '▋';
|
||||
animation: blink 1s infinite;
|
||||
color: #6065D9;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
@@ -112,10 +128,6 @@
|
||||
<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>
|
||||
@@ -413,13 +425,6 @@
|
||||
</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">
|
||||
@@ -552,9 +557,26 @@
|
||||
<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 class="mt-4 pt-4 border-t border-gray-200">
|
||||
<div id="modelAStats" class="flex items-center gap-4 text-sm text-dashboard-textLight">
|
||||
<span class="flex items-center gap-1">
|
||||
<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="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
字符: <span id="modelATokens">0</span>
|
||||
</span>
|
||||
<span>耗时: <span id="modelATime">-</span></span>
|
||||
<span class="text-dashboard-textLight">Token数: <span>-</span></span>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<button id="stopModelABtn" onclick="stopStreamingForModel('A')" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600 disabled:bg-gray-300 disabled:cursor-not-allowed" disabled>
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"></path>
|
||||
</svg>
|
||||
停止
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -579,9 +601,26 @@
|
||||
<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 class="mt-4 pt-4 border-t border-gray-200">
|
||||
<div id="modelBStats" class="flex items-center gap-4 text-sm text-dashboard-textLight">
|
||||
<span class="flex items-center gap-1">
|
||||
<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="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
字符: <span id="modelBTokens">0</span>
|
||||
</span>
|
||||
<span>耗时: <span id="modelBTime">-</span></span>
|
||||
<span class="text-dashboard-textLight">Token数: <span>-</span></span>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<button id="stopModelBBtn" onclick="stopStreamingForModel('B')" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600 disabled:bg-gray-300 disabled:cursor-not-allowed" disabled>
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"></path>
|
||||
</svg>
|
||||
停止
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1885,6 +1924,12 @@
|
||||
modelB: null
|
||||
};
|
||||
|
||||
// 流式输出状态管理
|
||||
let streamingState = {
|
||||
'A': { controllers: [], startTime: 0, completed: false },
|
||||
'B': { controllers: [], startTime: 0, completed: false }
|
||||
};
|
||||
|
||||
// 初始化模型对比页面
|
||||
async function initModelComparison() {
|
||||
await loadModelsFromAPI();
|
||||
@@ -2193,8 +2238,8 @@
|
||||
|
||||
// 并行运行两个模型(带超时控制)
|
||||
const [resultA, resultB] = await Promise.allSettled([
|
||||
withTimeout(callRealModel(modelA, prompt, temperature, maxTokens, modelA.streaming ? onChunkA : null), 60000), // 60秒超时
|
||||
withTimeout(callRealModel(modelB, prompt, temperature, maxTokens, modelB.streaming ? onChunkB : null), 60000) // 60秒超时
|
||||
withTimeout(callRealModel(modelA, prompt, temperature, maxTokens, modelA.streaming ? onChunkA : null, 'A'), 60000), // 60秒超时
|
||||
withTimeout(callRealModel(modelB, prompt, temperature, maxTokens, modelB.streaming ? onChunkB : null, 'B'), 60000) // 60秒超时
|
||||
]);
|
||||
|
||||
// 处理模型A结果
|
||||
@@ -2227,6 +2272,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 停止指定模型的流式输出
|
||||
function stopStreamingForModel(modelType) {
|
||||
if (streamingState[modelType] && streamingState[modelType].controllers.length > 0) {
|
||||
streamingState[modelType].controllers.forEach(controller => {
|
||||
if (controller && typeof controller.abort === 'function') {
|
||||
controller.abort();
|
||||
}
|
||||
});
|
||||
streamingState[modelType].controllers = [];
|
||||
streamingState[modelType].completed = true;
|
||||
|
||||
// 更新UI状态
|
||||
const outputElement = document.getElementById(`model${modelType}Output`);
|
||||
const statsElement = document.getElementById(`model${modelType}Stats`);
|
||||
const stopBtn = document.getElementById(`stopModel${modelType}Btn`);
|
||||
|
||||
if (outputElement) {
|
||||
outputElement.innerHTML += '<div class="mt-4 p-2 bg-orange-100 border border-orange-300 rounded text-orange-700 text-sm">⏸️ 输出已停止</div>';
|
||||
}
|
||||
|
||||
if (statsElement) {
|
||||
statsElement.innerHTML = `
|
||||
<div class="flex items-center gap-4 text-sm text-orange-600">
|
||||
<span class="flex items-center gap-1">
|
||||
<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="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"></path>
|
||||
</svg>
|
||||
已停止
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Promise超时控制工具函数
|
||||
function withTimeout(promise, timeoutMs) {
|
||||
return Promise.race([
|
||||
@@ -2238,10 +2323,24 @@
|
||||
}
|
||||
|
||||
// 调用真实模型API(支持流式输出)
|
||||
async function callRealModel(model, prompt, temperature, maxTokens, onChunk) {
|
||||
async function callRealModel(model, prompt, temperature, maxTokens, onChunk, modelType) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 创建 AbortController 用于支持中止操作
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
// 保存控制器到流式状态中
|
||||
if (modelType && streamingState[modelType]) {
|
||||
streamingState[modelType].controllers.push(controller);
|
||||
// 启用停止按钮
|
||||
const stopBtn = document.getElementById(`stopModel${modelType}Btn`);
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/models/${model.id}/call`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -2250,7 +2349,8 @@
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
stream: model.streaming || false
|
||||
})
|
||||
}),
|
||||
signal: signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -2294,6 +2394,7 @@
|
||||
}
|
||||
}
|
||||
if (parsed.done) {
|
||||
streamingState[modelType].completed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2390,17 +2491,44 @@
|
||||
const outputElement = document.getElementById(`model${modelType}Output`);
|
||||
const timeElement = document.getElementById(`model${modelType}Time`);
|
||||
const tokensElement = document.getElementById(`model${modelType}Tokens`);
|
||||
const statsElement = document.getElementById(`model${modelType}Stats`);
|
||||
|
||||
if (outputElement) {
|
||||
outputElement.innerHTML = `<div class="whitespace-pre-wrap text-dashboard-text">${result.output}</div>`;
|
||||
const isCompleted = streamingState[modelType]?.completed;
|
||||
const cursorClass = !isCompleted ? 'streaming-cursor' : '';
|
||||
outputElement.innerHTML = `
|
||||
<div class="streaming-content whitespace-pre-wrap text-dashboard-text font-mono ${cursorClass}">${result.output}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (timeElement) {
|
||||
timeElement.textContent = `${result.responseTime}ms`;
|
||||
const elapsed = Date.now() - streamingState[modelType].startTime;
|
||||
timeElement.textContent = `${elapsed}ms`;
|
||||
}
|
||||
|
||||
if (tokensElement) {
|
||||
tokensElement.textContent = result.tokenCount || '流式输出';
|
||||
tokensElement.textContent = result.output.length;
|
||||
}
|
||||
|
||||
// 添加统计信息
|
||||
if (statsElement) {
|
||||
statsElement.innerHTML = `
|
||||
<div class="flex items-center gap-4 text-sm text-dashboard-textLight">
|
||||
<span class="flex items-center gap-1">
|
||||
<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="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
字符: ${result.output.length}
|
||||
</span>
|
||||
${result.responseTime ? `<span class="flex items-center gap-1">
|
||||
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
耗时: ${result.responseTime}ms
|
||||
</span>` : ''}
|
||||
${!streamingState[modelType]?.completed ? '<span class="flex items-center gap-1 text-dashboard-primary"><span class="animate-pulse">●</span>生成中</span>' : '<span class="flex items-center gap-1 text-green-500">✓ 已完成</span>'}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2414,21 +2542,37 @@
|
||||
|
||||
// 重置输出区域
|
||||
function resetOutputAreas() {
|
||||
// 重置流式状态
|
||||
streamingState = {
|
||||
'A': { controllers: [], startTime: Date.now(), completed: false },
|
||||
'B': { controllers: [], startTime: Date.now(), completed: false }
|
||||
};
|
||||
|
||||
['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`);
|
||||
const statsElement = document.getElementById(`model${modelType}Stats`);
|
||||
const stopBtn = document.getElementById(`stopModel${modelType}Btn`);
|
||||
|
||||
if (outputElement) {
|
||||
outputElement.innerHTML = '<div class="text-dashboard-textLight">正在生成回答...</div>';
|
||||
outputElement.innerHTML = '<div class="text-dashboard-textLight flex items-center gap-2"><svg class="w-5 h-5 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>正在生成回答...</div>';
|
||||
}
|
||||
|
||||
if (timeElement) {
|
||||
timeElement.textContent = '-';
|
||||
timeElement.textContent = '0ms';
|
||||
}
|
||||
|
||||
if (tokensElement) {
|
||||
tokensElement.textContent = '-';
|
||||
tokensElement.textContent = '0';
|
||||
}
|
||||
|
||||
if (statsElement) {
|
||||
statsElement.innerHTML = '<div class="flex items-center gap-4 text-sm text-dashboard-textLight"><span class="flex items-center gap-1"><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="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>字符: 0</span><span class="flex items-center gap-1 text-dashboard-primary"><span class="animate-pulse">●</span>等待中</span></div>';
|
||||
}
|
||||
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
21
web/simple_server.py
Normal file
21
web/simple_server.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
|
||||
PORT = 9999
|
||||
DIRECTORY = "d:\\Code\\Project\\FT-Platform\\web"
|
||||
|
||||
class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
super().end_headers()
|
||||
|
||||
with socketserver.TCPServer(("", PORT), MyHTTPRequestHandler) as httpd:
|
||||
print(f"Server running at http://localhost:{PORT}/")
|
||||
httpd.serve_forever()
|
||||
Reference in New Issue
Block a user