1. 让AI修改了一些bug

This commit is contained in:
2026-01-16 11:49:05 +08:00
parent 43b6018a3e
commit 2ebd4ea09e
6 changed files with 2938 additions and 37 deletions

View File

@@ -70,7 +70,10 @@
"Bash(PYTHONPATH=src python -m uvicorn:*)", "Bash(PYTHONPATH=src python -m uvicorn:*)",
"Bash(ip route get 1.1.1.1)", "Bash(ip route get 1.1.1.1)",
"Bash(/d/Softwares/Anaconda/python:*)", "Bash(/d/Softwares/Anaconda/python:*)",
"Bash(set PYTHONPATH=src)" "Bash(set PYTHONPATH=src)",
"Bash(fuser:*)",
"Read(//d/Code/Project/FT-Platform/**)",
"Bash(xargs:*)"
] ]
} }
} }

View File

@@ -190,9 +190,8 @@ def setup_routes(app: FastAPI):
@app.get("/") @app.get("/")
async def root(): async def root():
"""根路径 - 重定向到前端监控界面""" """根路径 - 返回主界面"""
from fastapi.responses import RedirectResponse return FileResponse("../web/main.html")
return RedirectResponse(url="/dashboard")
@app.get("/dashboard") @app.get("/dashboard")
async def dashboard(): async def dashboard():
@@ -307,12 +306,6 @@ def setup_routes(app: FastAPI):
# 挂载静态文件目录 # 挂载静态文件目录
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
# 为前端页面创建特定路由
@app.get("/dashboard")
async def dashboard():
"""前端监控界面"""
return FileResponse("static/index.html")
# 只挂载静态资源CSS, JS, 图片等)- 如果目录存在 # 只挂载静态资源CSS, JS, 图片等)- 如果目录存在
import os import os
if os.path.exists("static"): if os.path.exists("static"):

57
web/index.html Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -64,6 +64,22 @@
background-color: #FFFFFF; background-color: #FFFFFF;
border-radius: 50%; 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> </style>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <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> <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> <span class="hidden md:block">强化训练</span>
</a></li> </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"> <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> <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> <span class="hidden md:block">模型对比</span>
@@ -413,13 +425,6 @@
</div> </div>
</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 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 class="flex flex-col h-full bg-gray-50">
@@ -552,9 +557,26 @@
<div class="text-dashboard-textLight">等待对比结果...</div> <div class="text-dashboard-textLight">等待对比结果...</div>
</div> </div>
</div> </div>
<div class="text-xs text-dashboard-textLight"> <div class="mt-4 pt-4 border-t border-gray-200">
<div>响应时间: <span id="modelATime">-</span></div> <div id="modelAStats" class="flex items-center gap-4 text-sm text-dashboard-textLight">
<div>Token数: <span id="modelATokens">-</span></div> <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>
</div> </div>
@@ -579,9 +601,26 @@
<div class="text-dashboard-textLight">等待对比结果...</div> <div class="text-dashboard-textLight">等待对比结果...</div>
</div> </div>
</div> </div>
<div class="text-xs text-dashboard-textLight"> <div class="mt-4 pt-4 border-t border-gray-200">
<div>响应时间: <span id="modelBTime">-</span></div> <div id="modelBStats" class="flex items-center gap-4 text-sm text-dashboard-textLight">
<div>Token数: <span id="modelBTokens">-</span></div> <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> </div>
</div> </div>
@@ -1885,6 +1924,12 @@
modelB: null modelB: null
}; };
// 流式输出状态管理
let streamingState = {
'A': { controllers: [], startTime: 0, completed: false },
'B': { controllers: [], startTime: 0, completed: false }
};
// 初始化模型对比页面 // 初始化模型对比页面
async function initModelComparison() { async function initModelComparison() {
await loadModelsFromAPI(); await loadModelsFromAPI();
@@ -2193,8 +2238,8 @@
// 并行运行两个模型(带超时控制) // 并行运行两个模型(带超时控制)
const [resultA, resultB] = await Promise.allSettled([ const [resultA, resultB] = await Promise.allSettled([
withTimeout(callRealModel(modelA, prompt, temperature, maxTokens, modelA.streaming ? onChunkA : 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), 60000) // 60秒超时 withTimeout(callRealModel(modelB, prompt, temperature, maxTokens, modelB.streaming ? onChunkB : null, 'B'), 60000) // 60秒超时
]); ]);
// 处理模型A结果 // 处理模型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超时控制工具函数 // Promise超时控制工具函数
function withTimeout(promise, timeoutMs) { function withTimeout(promise, timeoutMs) {
return Promise.race([ return Promise.race([
@@ -2238,10 +2323,24 @@
} }
// 调用真实模型API支持流式输出 // 调用真实模型API支持流式输出
async function callRealModel(model, prompt, temperature, maxTokens, onChunk) { async function callRealModel(model, prompt, temperature, maxTokens, onChunk, modelType) {
const startTime = Date.now(); const startTime = Date.now();
try { 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`, { const response = await fetch(`${API_BASE}/models/${model.id}/call`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -2250,7 +2349,8 @@
body: JSON.stringify({ body: JSON.stringify({
prompt: prompt, prompt: prompt,
stream: model.streaming || false stream: model.streaming || false
}) }),
signal: signal
}); });
if (!response.ok) { if (!response.ok) {
@@ -2294,6 +2394,7 @@
} }
} }
if (parsed.done) { if (parsed.done) {
streamingState[modelType].completed = true;
break; break;
} }
} }
@@ -2390,17 +2491,44 @@
const outputElement = document.getElementById(`model${modelType}Output`); const outputElement = document.getElementById(`model${modelType}Output`);
const timeElement = document.getElementById(`model${modelType}Time`); const timeElement = document.getElementById(`model${modelType}Time`);
const tokensElement = document.getElementById(`model${modelType}Tokens`); const tokensElement = document.getElementById(`model${modelType}Tokens`);
const statsElement = document.getElementById(`model${modelType}Stats`);
if (outputElement) { 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) { if (timeElement) {
timeElement.textContent = `${result.responseTime}ms`; const elapsed = Date.now() - streamingState[modelType].startTime;
timeElement.textContent = `${elapsed}ms`;
} }
if (tokensElement) { 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() { function resetOutputAreas() {
// 重置流式状态
streamingState = {
'A': { controllers: [], startTime: Date.now(), completed: false },
'B': { controllers: [], startTime: Date.now(), completed: false }
};
['A', 'B'].forEach(modelType => { ['A', 'B'].forEach(modelType => {
const outputElement = document.getElementById(`model${modelType}Output`); const outputElement = document.getElementById(`model${modelType}Output`);
const timeElement = document.getElementById(`model${modelType}Time`); const timeElement = document.getElementById(`model${modelType}Time`);
const tokensElement = document.getElementById(`model${modelType}Tokens`); const tokensElement = document.getElementById(`model${modelType}Tokens`);
const statsElement = document.getElementById(`model${modelType}Stats`);
const stopBtn = document.getElementById(`stopModel${modelType}Btn`);
if (outputElement) { 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) { if (timeElement) {
timeElement.textContent = '-'; timeElement.textContent = '0ms';
} }
if (tokensElement) { 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
View 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()