From 2ebd4ea09ed176f3ec4ea34cd40d3bf137b0df8e Mon Sep 17 00:00:00 2001 From: "DESKTOP-72TV0V4\\caoxiaozhu" Date: Fri, 16 Jan 2026 11:49:05 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E8=AE=A9AI=E4=BF=AE=E6=94=B9=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 5 +- request/src/core/app.py | 11 +- web/index.html | 57 + web/main.html | 2683 +++++++++++++++++++++++++++++++++++ web/pages/main.html | 198 ++- web/simple_server.py | 21 + 6 files changed, 2938 insertions(+), 37 deletions(-) create mode 100644 web/index.html create mode 100644 web/main.html create mode 100644 web/simple_server.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b35cdd3..f0d3434 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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:*)" ] } } diff --git a/request/src/core/app.py b/request/src/core/app.py index d92313d..ec77292 100644 --- a/request/src/core/app.py +++ b/request/src/core/app.py @@ -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"): diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..39ff9d9 --- /dev/null +++ b/web/index.html @@ -0,0 +1,57 @@ + + + + + + FT-Platform + + + +
+

🚀 FT-Platform

+

大模型微调平台

+ 进入平台 +
+ + \ No newline at end of file diff --git a/web/main.html b/web/main.html new file mode 100644 index 0000000..1ad0923 --- /dev/null +++ b/web/main.html @@ -0,0 +1,2683 @@ + + + + + + 大模型微调平台 - 主页 + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + diff --git a/web/pages/main.html b/web/pages/main.html index 90ab3e8..1ad0923 100644 --- a/web/pages/main.html +++ b/web/pages/main.html @@ -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; } + } } @@ -112,10 +128,6 @@ -
  • - - -
  • @@ -413,13 +425,6 @@ - - -
    -
    响应时间: -
    -
    Token数: -
    +
    +
    + + + + + 字符: 0 + + 耗时: - + Token数: - +
    +
    + +
    @@ -579,9 +601,26 @@
    等待对比结果...
    -
    -
    响应时间: -
    -
    Token数: -
    +
    +
    + + + + + 字符: 0 + + 耗时: - + Token数: - +
    +
    + +
    @@ -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 += '
    ⏸️ 输出已停止
    '; + } + + if (statsElement) { + statsElement.innerHTML = ` +
    + + + + + + 已停止 + +
    + `; + } + + 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 = `
    ${result.output}
    `; + const isCompleted = streamingState[modelType]?.completed; + const cursorClass = !isCompleted ? 'streaming-cursor' : ''; + outputElement.innerHTML = ` +
    ${result.output}
    + `; } 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 = ` +
    + + + + + 字符: ${result.output.length} + + ${result.responseTime ? ` + + + + 耗时: ${result.responseTime}ms + ` : ''} + ${!streamingState[modelType]?.completed ? '生成中' : '✓ 已完成'} +
    + `; } } @@ -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 = '
    正在生成回答...
    '; + outputElement.innerHTML = '
    正在生成回答...
    '; } if (timeElement) { - timeElement.textContent = '-'; + timeElement.textContent = '0ms'; } if (tokensElement) { - tokensElement.textContent = '-'; + tokensElement.textContent = '0'; + } + + if (statsElement) { + statsElement.innerHTML = '
    字符: 0等待中
    '; + } + + if (stopBtn) { + stopBtn.disabled = true; } }); diff --git a/web/simple_server.py b/web/simple_server.py new file mode 100644 index 0000000..734480e --- /dev/null +++ b/web/simple_server.py @@ -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() \ No newline at end of file