Compare commits
6 Commits
main
...
v1_interfa
| Author | SHA1 | Date | |
|---|---|---|---|
| 373f5244e5 | |||
| cde34e4919 | |||
| 1ae1139f17 | |||
| 7250d5840a | |||
| ea8d5a28dd | |||
| b2f19d9583 |
@@ -32,7 +32,8 @@
|
||||
"Bash(find:*)",
|
||||
"Bash(./test_upload.sh:*)",
|
||||
"Bash(./test_all.sh)",
|
||||
"Bash(/data/code/FT_Platform/YG_FT_Platform/test_data_dir.sh:*)"
|
||||
"Bash(/data/code/FT_Platform/YG_FT_Platform/test_data_dir.sh:*)",
|
||||
"Bash(grep:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
17
config.yaml
Normal file
17
config.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# 数据库配置
|
||||
database:
|
||||
host: "10.10.10.189"
|
||||
port: 3306
|
||||
username: "root"
|
||||
password: "88116142"
|
||||
name: "ft"
|
||||
charset: "utf8mb4"
|
||||
|
||||
# 应用配置
|
||||
app:
|
||||
host: "0.0.0.0"
|
||||
port: 8080
|
||||
debug: true
|
||||
|
||||
# 密钥配置
|
||||
secret_key: "yg-ft-platform-secret-key-2024"
|
||||
3
data/.gitignore
vendored
3
data/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
# 忽略data目录下的所有文件
|
||||
*
|
||||
!.gitignore
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
pymysql==1.1.0
|
||||
pyyaml==6.0.1
|
||||
cryptography==41.0.7
|
||||
129
src/README.md
129
src/README.md
@@ -1,129 +0,0 @@
|
||||
# FastAPI 服务器
|
||||
|
||||
## 功能特性
|
||||
|
||||
这个 FastAPI 服务器为大模型微调平台提供了 RESTful API 接口。
|
||||
|
||||
## API 端点
|
||||
|
||||
### 基础信息
|
||||
- `GET /` - 根路径,返回欢迎信息
|
||||
- `GET /api/health` - 健康检查
|
||||
|
||||
### 用户认证
|
||||
- `POST /api/login` - 用户登录
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "your_password"
|
||||
}
|
||||
```
|
||||
|
||||
### 数据集管理
|
||||
- `GET /api/datasets` - 获取数据集列表
|
||||
- `POST /api/datasets` - 创建新数据集
|
||||
```json
|
||||
{
|
||||
"name": "新数据集名称",
|
||||
"description": "数据集描述",
|
||||
"size": "数据集大小"
|
||||
}
|
||||
```
|
||||
- `POST /api/datasets/upload` - 上传数据集文件(支持 JSON 和 JSONL 格式)
|
||||
```bash
|
||||
curl -X POST "http://10.10.10.77:8001/api/datasets/upload" \
|
||||
-F "file=@dataset.json" \
|
||||
-F "description=数据集描述"
|
||||
```
|
||||
**支持的文件格式**: .json, .jsonl
|
||||
**文件大小限制**: 100MB
|
||||
- `GET /api/datasets/files` - 获取data目录中保存的文件列表
|
||||
- `DELETE /api/datasets/{dataset_id}` - 删除数据集
|
||||
|
||||
### 模型管理
|
||||
- `GET /api/models` - 获取模型列表
|
||||
- `POST /api/models/config` - 配置模型参数
|
||||
```json
|
||||
{
|
||||
"model_name": "GPT-4",
|
||||
"learning_rate": 0.001,
|
||||
"batch_size": 32,
|
||||
"epochs": 100
|
||||
}
|
||||
```
|
||||
|
||||
### 训练管理
|
||||
- `GET /api/training/status` - 获取训练状态
|
||||
- `POST /api/training/start` - 开始训练任务
|
||||
- `POST /api/training/stop/{task_id}` - 停止训练任务
|
||||
- `GET /api/model/{model_id}/metrics` - 获取模型指标
|
||||
|
||||
### 系统监控
|
||||
- `GET /api/system/stats` - 获取系统统计信息
|
||||
|
||||
## 启动服务器
|
||||
|
||||
### 方法 1: 使用启动脚本(推荐)
|
||||
```bash
|
||||
cd src
|
||||
./run.sh
|
||||
```
|
||||
|
||||
### 方法 2: 手动启动
|
||||
```bash
|
||||
# 安装依赖
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
# 启动服务器
|
||||
uvicorn main:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
## 访问地址
|
||||
|
||||
- **服务器**: http://10.10.10.77:8001
|
||||
- **API 文档**: http://10.10.10.77:8001/docs
|
||||
- **替代文档**: http://10.10.10.77:8001/redoc
|
||||
|
||||
## 示例请求
|
||||
|
||||
### 登录
|
||||
```bash
|
||||
curl -X POST "http://10.10.10.77:8001/api/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "123456"}'
|
||||
```
|
||||
|
||||
### 获取数据集列表
|
||||
```bash
|
||||
curl -X GET "http://10.10.10.77:8001/api/datasets"
|
||||
```
|
||||
|
||||
### 上传数据集文件
|
||||
```bash
|
||||
curl -X POST "http://10.10.10.77:8001/api/datasets/upload" \
|
||||
-F "file=@dataset.json" \
|
||||
-F "description=数据集描述"
|
||||
```
|
||||
|
||||
### 获取data目录文件列表
|
||||
```bash
|
||||
curl -X GET "http://10.10.10.77:8001/api/datasets/files"
|
||||
```
|
||||
|
||||
### 获取系统统计
|
||||
```bash
|
||||
curl -X GET "http://10.10.10.77:8001/api/system/stats"
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python 3.7+
|
||||
- FastAPI 0.104.1
|
||||
- Uvicorn 0.24.0
|
||||
- Pydantic 2.5.0
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 服务器默认运行在端口 8001
|
||||
- 使用 `--reload` 参数启用热重载
|
||||
- 所有 API 响应都遵循统一格式
|
||||
843
src/main.py
843
src/main.py
@@ -1,381 +1,506 @@
|
||||
from fastapi import FastAPI, File, UploadFile, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
import uvicorn
|
||||
"""
|
||||
远光软件微调平台 - Flask 后端 API
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import pymysql
|
||||
import yaml
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = FastAPI(title="大模型微调平台 API", version="1.0.0")
|
||||
# 获取项目根目录
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, PROJECT_ROOT)
|
||||
|
||||
# 加载配置
|
||||
CONFIG_PATH = os.path.join(PROJECT_ROOT, 'config.yaml')
|
||||
|
||||
|
||||
# 请求模型
|
||||
class UserModel(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
def load_config():
|
||||
"""加载配置文件"""
|
||||
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
class DatasetModel(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
size: str
|
||||
CONFIG = load_config()
|
||||
|
||||
|
||||
class ModelConfigModel(BaseModel):
|
||||
model_name: str
|
||||
learning_rate: float
|
||||
batch_size: int
|
||||
epochs: int
|
||||
|
||||
|
||||
# 响应模型
|
||||
class ResponseModel(BaseModel):
|
||||
code: int
|
||||
message: str
|
||||
data: Optional[dict] = None
|
||||
|
||||
|
||||
# 模拟数据存储
|
||||
datasets = [
|
||||
{"id": 1, "name": "中文对话数据集", "size": "1.2GB", "status": "已处理"},
|
||||
{"id": 2, "name": "英文文本分类数据集", "size": "856MB", "status": "处理中"},
|
||||
{"id": 3, "name": "图像识别数据集", "size": "2.5GB", "status": "待处理"},
|
||||
]
|
||||
|
||||
models = [
|
||||
{"id": 1, "name": "GPT-4", "status": "训练中", "accuracy": "92%"},
|
||||
{"id": 2, "name": "BERT", "status": "已完成", "accuracy": "89%"},
|
||||
{"id": 3, "name": "LLaMA", "status": "已完成", "accuracy": "95%"},
|
||||
]
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""根路径"""
|
||||
return {"message": "大模型微调平台 API 服务"}
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health_check():
|
||||
"""健康检查"""
|
||||
return ResponseModel(code=200, message="服务运行正常", data={"status": "healthy"})
|
||||
|
||||
|
||||
@app.post("/api/login", response_model=ResponseModel)
|
||||
async def login(user: UserModel):
|
||||
"""用户登录"""
|
||||
if user.username == "admin" and user.password:
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="登录成功",
|
||||
data={"token": "mock_token_12345", "user": user.username}
|
||||
)
|
||||
else:
|
||||
return ResponseModel(code=401, message="用户名或密码错误")
|
||||
|
||||
|
||||
@app.get("/api/datasets", response_model=ResponseModel)
|
||||
async def get_datasets():
|
||||
"""获取数据集列表"""
|
||||
return ResponseModel(code=200, message="获取成功", data={"datasets": datasets})
|
||||
|
||||
|
||||
@app.post("/api/datasets", response_model=ResponseModel)
|
||||
async def create_dataset(dataset: DatasetModel):
|
||||
"""创建数据集"""
|
||||
new_dataset = {
|
||||
"id": len(datasets) + 1,
|
||||
"name": dataset.name,
|
||||
"description": dataset.description,
|
||||
"size": "0MB",
|
||||
"status": "待处理"
|
||||
}
|
||||
datasets.append(new_dataset)
|
||||
return ResponseModel(code=201, message="创建成功", data={"dataset": new_dataset})
|
||||
|
||||
|
||||
@app.post("/api/datasets/upload", response_model=ResponseModel)
|
||||
async def upload_dataset(file: UploadFile = File(...), description: Optional[str] = None):
|
||||
"""上传数据集文件(仅支持 JSON 和 JSONL 格式)"""
|
||||
# 检查文件类型
|
||||
allowed_extensions = ['.json', '.jsonl']
|
||||
file_extension = os.path.splitext(file.filename)[1].lower()
|
||||
|
||||
if file_extension not in allowed_extensions:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"不支持的文件类型。只能上传 {', '.join(allowed_extensions)} 格式的文件"
|
||||
)
|
||||
|
||||
# 检查文件大小(限制为 100MB)
|
||||
max_size = 100 * 1024 * 1024 # 100MB
|
||||
contents = await file.read()
|
||||
file_size = len(contents)
|
||||
|
||||
if file_size > max_size:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"文件大小超过限制。最大支持 100MB,当前文件大小: {file_size / (1024*1024):.2f}MB"
|
||||
)
|
||||
|
||||
try:
|
||||
# 验证文件内容
|
||||
if file_extension == '.json':
|
||||
# 验证 JSON 文件
|
||||
json.loads(contents.decode('utf-8'))
|
||||
elif file_extension == '.jsonl':
|
||||
# 验证 JSONL 文件(每行必须是有效的 JSON)
|
||||
lines = contents.decode('utf-8').strip().split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip():
|
||||
try:
|
||||
json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"JSONL 文件格式错误:第 {i+1} 行不是有效的 JSON 格式"
|
||||
)
|
||||
|
||||
# 生成文件大小字符串
|
||||
if file_size < 1024:
|
||||
size_str = f"{file_size}B"
|
||||
elif file_size < 1024 * 1024:
|
||||
size_str = f"{file_size / 1024:.2f}KB"
|
||||
else:
|
||||
size_str = f"{file_size / (1024*1024):.2f}MB"
|
||||
|
||||
# 计算行数(用于统计)
|
||||
lines_count = len(contents.decode('utf-8').strip().split('\n')) if contents else 0
|
||||
|
||||
# 保存文件到 data 目录
|
||||
data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data')
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
# 生成唯一文件名(避免冲突)
|
||||
base_name = os.path.splitext(file.filename)[0]
|
||||
timestamp = int(time.time())
|
||||
saved_filename = f"{base_name}_{timestamp}{file_extension}"
|
||||
saved_path = os.path.join(data_dir, saved_filename)
|
||||
|
||||
# 写入文件
|
||||
with open(saved_path, 'wb') as f:
|
||||
f.write(contents)
|
||||
|
||||
# 创建新数据集记录
|
||||
new_dataset = {
|
||||
"id": len(datasets) + 1,
|
||||
"name": file.filename,
|
||||
"description": description or f"上传的数据集文件,包含 {lines_count} 行数据",
|
||||
"size": size_str,
|
||||
"status": "已处理",
|
||||
"upload_time": "刚刚",
|
||||
"file_extension": file_extension,
|
||||
"records_count": lines_count,
|
||||
"saved_path": saved_path # 添加保存路径信息
|
||||
}
|
||||
|
||||
# 添加到数据集列表
|
||||
datasets.append(new_dataset)
|
||||
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="文件上传成功",
|
||||
data={
|
||||
"dataset": new_dataset,
|
||||
"file_info": {
|
||||
"filename": file.filename,
|
||||
"size": size_str,
|
||||
"extension": file_extension,
|
||||
"records": lines_count
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="JSON 文件格式错误:文件内容不是有效的 JSON 格式"
|
||||
)
|
||||
except UnicodeDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="文件编码错误:请确保文件使用 UTF-8 编码"
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"文件处理错误:{str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/datasets/files", response_model=ResponseModel)
|
||||
async def list_dataset_files():
|
||||
"""列出data目录中所有保存的数据集文件"""
|
||||
try:
|
||||
data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data')
|
||||
|
||||
if not os.path.exists(data_dir):
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="获取成功",
|
||||
data={"files": [], "total": 0, "directory": data_dir}
|
||||
)
|
||||
|
||||
files = []
|
||||
for filename in os.listdir(data_dir):
|
||||
file_path = os.path.join(data_dir, filename)
|
||||
if os.path.isfile(file_path):
|
||||
stat = os.stat(file_path)
|
||||
files.append({
|
||||
"filename": filename,
|
||||
"size": stat.st_size,
|
||||
"size_human": format_size(stat.st_size),
|
||||
"modified_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(stat.st_mtime)),
|
||||
"path": file_path
|
||||
})
|
||||
|
||||
# 按修改时间排序(最新的在前)
|
||||
files.sort(key=lambda x: x["modified_time"], reverse=True)
|
||||
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="获取成功",
|
||||
data={
|
||||
"files": files,
|
||||
"total": len(files),
|
||||
"directory": data_dir
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"获取文件列表失败:{str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def format_size(size_bytes):
|
||||
"""格式化文件大小"""
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes}B"
|
||||
elif size_bytes < 1024 * 1024:
|
||||
return f"{size_bytes / 1024:.2f}KB"
|
||||
else:
|
||||
return f"{size_bytes / (1024*1024):.2f}MB"
|
||||
|
||||
|
||||
@app.delete("/api/datasets/{dataset_id}", response_model=ResponseModel)
|
||||
async def delete_dataset(dataset_id: int):
|
||||
"""删除数据集"""
|
||||
global datasets
|
||||
for i, dataset in enumerate(datasets):
|
||||
if dataset["id"] == dataset_id:
|
||||
deleted_dataset = datasets.pop(i)
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="删除成功",
|
||||
data={"deleted_dataset": deleted_dataset}
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="数据集不存在")
|
||||
|
||||
|
||||
@app.get("/api/models", response_model=ResponseModel)
|
||||
async def get_models():
|
||||
"""获取模型列表"""
|
||||
return ResponseModel(code=200, message="获取成功", data={"models": models})
|
||||
|
||||
|
||||
@app.post("/api/models/config", response_model=ResponseModel)
|
||||
async def config_model(config: ModelConfigModel):
|
||||
"""配置模型参数"""
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="配置成功",
|
||||
data={
|
||||
"model_name": config.model_name,
|
||||
"learning_rate": config.learning_rate,
|
||||
"batch_size": config.batch_size,
|
||||
"epochs": config.epochs,
|
||||
"status": "已配置"
|
||||
}
|
||||
def get_db_connection():
|
||||
"""获取数据库连接"""
|
||||
db_config = CONFIG['database']
|
||||
return pymysql.connect(
|
||||
host=db_config['host'],
|
||||
port=db_config['port'],
|
||||
user=db_config['username'],
|
||||
password=db_config['password'],
|
||||
database=db_config['name'],
|
||||
charset=db_config.get('charset', 'utf8mb4'),
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/training/status")
|
||||
async def get_training_status():
|
||||
"""获取训练状态"""
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="获取成功",
|
||||
data={
|
||||
"current_task": "GPT-4微调",
|
||||
"progress": 75,
|
||||
"eta": "2小时",
|
||||
"loss": 0.23,
|
||||
"accuracy": 0.89
|
||||
}
|
||||
)
|
||||
def init_database():
|
||||
"""初始化数据库表"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
tables = [
|
||||
# 精调训练表
|
||||
"""CREATE TABLE IF NOT EXISTS fine_tune (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
base_model VARCHAR(255),
|
||||
train_type VARCHAR(50),
|
||||
train_method VARCHAR(50),
|
||||
dataset_id INT,
|
||||
valid_split VARCHAR(50),
|
||||
valid_ratio INT DEFAULT 10,
|
||||
output_model_name VARCHAR(255),
|
||||
status VARCHAR(50) DEFAULT 'pending',
|
||||
progress INT DEFAULT 0,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 我的模型表
|
||||
"""CREATE TABLE IF NOT EXISTS my_models (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100),
|
||||
version VARCHAR(50),
|
||||
description TEXT,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 模型评测表
|
||||
"""CREATE TABLE IF NOT EXISTS model_eval (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
dataset VARCHAR(255),
|
||||
metric VARCHAR(100),
|
||||
score DECIMAL(10, 4),
|
||||
status VARCHAR(50) DEFAULT 'completed',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 模型部署表
|
||||
"""CREATE TABLE IF NOT EXISTS model_deploy (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
endpoint VARCHAR(255),
|
||||
instance VARCHAR(100),
|
||||
status VARCHAR(50) DEFAULT 'running',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 数据集管理表
|
||||
"""CREATE TABLE IF NOT EXISTS dataset_manage (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100),
|
||||
size VARCHAR(50),
|
||||
count INT,
|
||||
description TEXT,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 数据生成表
|
||||
"""CREATE TABLE IF NOT EXISTS data_generate (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
template VARCHAR(255),
|
||||
count INT DEFAULT 0,
|
||||
status VARCHAR(50) DEFAULT 'pending',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 权限管理表
|
||||
"""CREATE TABLE IF NOT EXISTS permission (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(100) NOT NULL,
|
||||
role VARCHAR(50) DEFAULT 'user',
|
||||
permissions TEXT,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 模型管理表
|
||||
"""CREATE TABLE IF NOT EXISTS model_manage (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100),
|
||||
version VARCHAR(50),
|
||||
path VARCHAR(500),
|
||||
description TEXT,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 系统配置表
|
||||
"""CREATE TABLE IF NOT EXISTS sys_config (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
config_value TEXT,
|
||||
description VARCHAR(255),
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)""",
|
||||
|
||||
# 用户表
|
||||
"""CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(100) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) DEFAULT 'user',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)"""
|
||||
]
|
||||
|
||||
for table_sql in tables:
|
||||
cursor.execute(table_sql)
|
||||
|
||||
# 插入默认管理员用户
|
||||
cursor.execute("SELECT * FROM users WHERE username = 'admin'")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("INSERT INTO users (username, password, role) VALUES ('admin', 'admin', 'admin')")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
print("数据库初始化完成")
|
||||
|
||||
|
||||
@app.get("/api/system/stats")
|
||||
async def get_system_stats():
|
||||
"""获取系统统计信息"""
|
||||
import random
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="获取成功",
|
||||
data={
|
||||
"cpu_usage": random.randint(30, 80),
|
||||
"memory_usage": random.randint(40, 70),
|
||||
"gpu_usage": random.randint(50, 90),
|
||||
"active_tasks": 5,
|
||||
"completed_tasks": 158
|
||||
}
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = CONFIG['secret_key']
|
||||
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
|
||||
@app.post("/api/training/start")
|
||||
async def start_training(model_name: str, dataset_id: int):
|
||||
"""开始训练任务"""
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="训练任务已启动",
|
||||
data={
|
||||
"task_id": random.randint(1000, 9999),
|
||||
"model_name": model_name,
|
||||
"dataset_id": dataset_id,
|
||||
"status": "running"
|
||||
}
|
||||
)
|
||||
# ============ 健康检查 ============
|
||||
@app.route('/api/health', methods=['GET'])
|
||||
def health_check():
|
||||
"""健康检查接口"""
|
||||
return jsonify({'status': 'ok', 'code': 0})
|
||||
|
||||
|
||||
@app.post("/api/training/stop/{task_id}")
|
||||
async def stop_training(task_id: int):
|
||||
"""停止训练任务"""
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message=f"训练任务 {task_id} 已停止",
|
||||
data={"task_id": task_id, "status": "stopped"}
|
||||
)
|
||||
# ============ 通用 CRUD 操作 ============
|
||||
def generic_get_all(table_name, order_by='create_time DESC'):
|
||||
"""通用查询所有"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM {table_name} ORDER BY {order_by}")
|
||||
result = cursor.fetchall()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/model/{model_id}/metrics")
|
||||
async def get_model_metrics(model_id: int):
|
||||
"""获取模型指标"""
|
||||
return ResponseModel(
|
||||
code=200,
|
||||
message="获取成功",
|
||||
data={
|
||||
"model_id": model_id,
|
||||
"accuracy": round(random.uniform(0.85, 0.98), 3),
|
||||
"precision": round(random.uniform(0.80, 0.95), 3),
|
||||
"recall": round(random.uniform(0.82, 0.96), 3),
|
||||
"f1_score": round(random.uniform(0.83, 0.97), 3),
|
||||
"training_time": f"{random.randint(2, 24)}小时",
|
||||
"parameters": random.randint(1000000, 100000000)
|
||||
}
|
||||
)
|
||||
def generic_get_by_id(table_name, id_val):
|
||||
"""通用按ID查询"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM {table_name} WHERE id = %s", (id_val,))
|
||||
result = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8001)
|
||||
def generic_create(table_name, data):
|
||||
"""通用创建"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
columns = ', '.join(data.keys())
|
||||
placeholders = ', '.join(['%s'] * len(data))
|
||||
sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
|
||||
cursor.execute(sql, list(data.values()))
|
||||
conn.commit()
|
||||
new_id = cursor.lastrowid
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return new_id
|
||||
|
||||
|
||||
def generic_update(table_name, id_val, data):
|
||||
"""通用更新"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
set_clause = ', '.join([f"{k} = %s" for k in data.keys()])
|
||||
sql = f"UPDATE {table_name} SET {set_clause} WHERE id = %s"
|
||||
values = list(data.values()) + [id_val]
|
||||
cursor.execute(sql, values)
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
def generic_delete(table_name, id_val):
|
||||
"""通用删除"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"DELETE FROM {table_name} WHERE id = %s", (id_val,))
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
# ============ 登录接口 ============
|
||||
@app.route('/api/login', methods=['POST'])
|
||||
def login():
|
||||
data = request.json
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
|
||||
user = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if user:
|
||||
return jsonify({'code': 0, 'message': '登录成功', 'data': {'username': user['username'], 'role': user['role']}})
|
||||
return jsonify({'code': 1, 'message': '用户名或密码错误'})
|
||||
|
||||
|
||||
# ============ 精调训练接口 ============
|
||||
@app.route('/api/fine-tune', methods=['GET'])
|
||||
def get_fine_tune():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('fine_tune')})
|
||||
|
||||
|
||||
@app.route('/api/fine-tune', methods=['POST'])
|
||||
def create_fine_tune():
|
||||
data = request.json
|
||||
new_id = generic_create('fine_tune', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/fine-tune/<int:id>', methods=['PUT'])
|
||||
def update_fine_tune(id):
|
||||
data = request.json
|
||||
generic_update('fine_tune', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/fine-tune/<int:id>', methods=['DELETE'])
|
||||
def delete_fine_tune(id):
|
||||
generic_delete('fine_tune', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 我的模型接口 ============
|
||||
@app.route('/api/my-models', methods=['GET'])
|
||||
def get_my_models():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('my_models')})
|
||||
|
||||
|
||||
@app.route('/api/my-models', methods=['POST'])
|
||||
def create_my_model():
|
||||
data = request.json
|
||||
new_id = generic_create('my_models', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/my-models/<int:id>', methods=['PUT'])
|
||||
def update_my_model(id):
|
||||
data = request.json
|
||||
generic_update('my_models', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/my-models/<int:id>', methods=['DELETE'])
|
||||
def delete_my_model(id):
|
||||
generic_delete('my_models', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型评测接口 ============
|
||||
@app.route('/api/model-eval', methods=['GET'])
|
||||
def get_model_eval():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_eval')})
|
||||
|
||||
|
||||
@app.route('/api/model-eval', methods=['POST'])
|
||||
def create_model_eval():
|
||||
data = request.json
|
||||
new_id = generic_create('model_eval', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/model-eval/<int:id>', methods=['PUT'])
|
||||
def update_model_eval(id):
|
||||
data = request.json
|
||||
generic_update('model_eval', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/model-eval/<int:id>', methods=['DELETE'])
|
||||
def delete_model_eval(id):
|
||||
generic_delete('model_eval', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型部署接口 ============
|
||||
@app.route('/api/model-deploy', methods=['GET'])
|
||||
def get_model_deploy():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_deploy')})
|
||||
|
||||
|
||||
@app.route('/api/model-deploy', methods=['POST'])
|
||||
def create_model_deploy():
|
||||
data = request.json
|
||||
new_id = generic_create('model_deploy', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/model-deploy/<int:id>', methods=['PUT'])
|
||||
def update_model_deploy(id):
|
||||
data = request.json
|
||||
generic_update('model_deploy', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/model-deploy/<int:id>', methods=['DELETE'])
|
||||
def delete_model_deploy(id):
|
||||
generic_delete('model_deploy', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 数据集管理接口 ============
|
||||
@app.route('/api/dataset-manage', methods=['GET'])
|
||||
def get_dataset_manage():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('dataset_manage')})
|
||||
|
||||
|
||||
@app.route('/api/dataset-manage', methods=['POST'])
|
||||
def create_dataset_manage():
|
||||
data = request.json
|
||||
new_id = generic_create('dataset_manage', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/dataset-manage/<int:id>', methods=['PUT'])
|
||||
def update_dataset_manage(id):
|
||||
data = request.json
|
||||
generic_update('dataset_manage', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/dataset-manage/<int:id>', methods=['DELETE'])
|
||||
def delete_dataset_manage(id):
|
||||
generic_delete('dataset_manage', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 数据生成接口 ============
|
||||
@app.route('/api/data-generate', methods=['GET'])
|
||||
def get_data_generate():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('data_generate')})
|
||||
|
||||
|
||||
@app.route('/api/data-generate', methods=['POST'])
|
||||
def create_data_generate():
|
||||
data = request.json
|
||||
new_id = generic_create('data_generate', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/data-generate/<int:id>', methods=['PUT'])
|
||||
def update_data_generate(id):
|
||||
data = request.json
|
||||
generic_update('data_generate', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/data-generate/<int:id>', methods=['DELETE'])
|
||||
def delete_data_generate(id):
|
||||
generic_delete('data_generate', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 权限管理接口 ============
|
||||
@app.route('/api/permission', methods=['GET'])
|
||||
def get_permission():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('permission')})
|
||||
|
||||
|
||||
@app.route('/api/permission', methods=['POST'])
|
||||
def create_permission():
|
||||
data = request.json
|
||||
new_id = generic_create('permission', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/permission/<int:id>', methods=['PUT'])
|
||||
def update_permission(id):
|
||||
data = request.json
|
||||
generic_update('permission', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/permission/<int:id>', methods=['DELETE'])
|
||||
def delete_permission(id):
|
||||
generic_delete('permission', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型管理接口 ============
|
||||
@app.route('/api/model-manage', methods=['GET'])
|
||||
def get_model_manage():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_manage')})
|
||||
|
||||
|
||||
@app.route('/api/model-manage', methods=['POST'])
|
||||
def create_model_manage():
|
||||
data = request.json
|
||||
new_id = generic_create('model_manage', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/model-manage/<int:id>', methods=['PUT'])
|
||||
def update_model_manage(id):
|
||||
data = request.json
|
||||
generic_update('model_manage', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/model-manage/<int:id>', methods=['DELETE'])
|
||||
def delete_model_manage(id):
|
||||
generic_delete('model_manage', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 系统配置接口 ============
|
||||
@app.route('/api/sys-config', methods=['GET'])
|
||||
def get_sys_config():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('sys_config')})
|
||||
|
||||
|
||||
@app.route('/api/sys-config', methods=['POST'])
|
||||
def create_sys_config():
|
||||
data = request.json
|
||||
new_id = generic_create('sys_config', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/sys-config/<int:id>', methods=['PUT'])
|
||||
def update_sys_config(id):
|
||||
data = request.json
|
||||
generic_update('sys_config', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/sys-config/<int:id>', methods=['DELETE'])
|
||||
def delete_sys_config(id):
|
||||
generic_delete('sys_config', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_database()
|
||||
app_config = CONFIG['app']
|
||||
app.run(host=app_config['host'], port=app_config['port'], debug=app_config.get('debug', True))
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
pydantic==2.5.0
|
||||
python-multipart==0.0.6
|
||||
47
src/run.sh
Executable file → Normal file
47
src/run.sh
Executable file → Normal file
@@ -1,43 +1,14 @@
|
||||
#!/bin/bash
|
||||
# 启动远光软件微调平台后端服务
|
||||
|
||||
echo "🚀 启动 FastAPI 服务器..."
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# 确保在正确的目录中
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "📂 当前目录: $SCRIPT_DIR"
|
||||
|
||||
# 检查Python是否安装
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ 错误: Python3 未安装"
|
||||
echo "请先安装 Python3"
|
||||
exit 1
|
||||
# 检查并安装依赖
|
||||
if ! python3 -c "import flask" 2>/dev/null; then
|
||||
echo "正在安装依赖..."
|
||||
pip install -r ../requirements.txt
|
||||
fi
|
||||
|
||||
# 检查pip是否安装
|
||||
if ! command -v pip3 &> /dev/null; then
|
||||
echo "❌ 错误: pip3 未安装"
|
||||
echo "请先安装 pip3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装依赖
|
||||
echo "📦 安装依赖包..."
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ 依赖安装失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🌐 服务器地址: http://localhost:8001"
|
||||
echo "📚 API 文档: http://localhost:8001/docs"
|
||||
echo "🔍 替代文档: http://localhost:8001/redoc"
|
||||
echo ""
|
||||
echo "按 Ctrl+C 停止服务器"
|
||||
echo ""
|
||||
|
||||
# 启动服务器
|
||||
python3 -m uvicorn main:app --host 0.0.0.0 --port 8001 --reload
|
||||
# 启动服务
|
||||
echo "启动后端服务..."
|
||||
python3 main.py
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🧪 测试 FastAPI 服务器"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
BASE_URL="http://localhost:8001"
|
||||
|
||||
# 测试 1: 根路径
|
||||
echo "1. 测试根路径..."
|
||||
curl -s "$BASE_URL/" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 2: 健康检查
|
||||
echo "2. 测试健康检查..."
|
||||
curl -s "$BASE_URL/api/health" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 3: 用户登录
|
||||
echo "3. 测试用户登录..."
|
||||
curl -s -X POST "$BASE_URL/api/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "123456"}' | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 4: 获取数据集列表
|
||||
echo "4. 测试获取数据集列表..."
|
||||
curl -s "$BASE_URL/api/datasets" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 5: 获取模型列表
|
||||
echo "5. 测试获取模型列表..."
|
||||
curl -s "$BASE_URL/api/models" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 6: 系统统计
|
||||
echo "6. 测试系统统计..."
|
||||
curl -s "$BASE_URL/api/system/stats" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
# 测试 7: 训练状态
|
||||
echo "7. 测试训练状态..."
|
||||
curl -s "$BASE_URL/api/training/status" | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
echo "=================================="
|
||||
echo "✅ 所有测试完成!"
|
||||
88
test_all.sh
88
test_all.sh
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🧪 测试大模型微调平台 - 所有服务"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
BASE_URL="http://localhost"
|
||||
|
||||
# 检查服务是否运行
|
||||
echo "1. 检查服务状态..."
|
||||
echo ""
|
||||
|
||||
# 检查端口 8000 (Web 前端)
|
||||
if curl -s "${BASE_URL}:8000" > /dev/null 2>&1; then
|
||||
echo "✅ Web 前端服务正在运行 (端口 8000)"
|
||||
else
|
||||
echo "❌ Web 前端服务未运行 (端口 8000)"
|
||||
fi
|
||||
|
||||
# 检查端口 8001 (FastAPI)
|
||||
if curl -s "${BASE_URL}:8001" > /dev/null 2>&1; then
|
||||
echo "✅ FastAPI 服务正在运行 (端口 8001)"
|
||||
else
|
||||
echo "❌ FastAPI 服务未运行 (端口 8001)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# 获取本机IP
|
||||
SERVER_IP=$(hostname -I | awk '{print $1}')
|
||||
|
||||
echo "📱 访问地址:"
|
||||
echo ""
|
||||
echo "前端页面:"
|
||||
echo " - 主页: http://$SERVER_IP:8000/pages/main.html"
|
||||
echo " - 登录: http://$SERVER_IP:8000/pages/login.html"
|
||||
echo ""
|
||||
echo "API 服务:"
|
||||
echo " - API 根路径: http://$SERVER_IP:8001/"
|
||||
echo " - API 健康检查: http://$SERVER_IP:8001/api/health"
|
||||
echo " - API 文档: http://$SERVER_IP:8001/docs"
|
||||
echo ""
|
||||
|
||||
echo "=================================="
|
||||
echo "2. 测试 API 接口..."
|
||||
echo ""
|
||||
|
||||
# 测试 API
|
||||
echo "测试根路径:"
|
||||
curl -s "${BASE_URL}:8001/" | python3 -m json.tool 2>/dev/null || curl -s "${BASE_URL}:8001/"
|
||||
echo ""
|
||||
|
||||
echo "测试健康检查:"
|
||||
curl -s "${BASE_URL}:8001/api/health" | python3 -m json.tool 2>/dev/null || echo "请求失败"
|
||||
echo ""
|
||||
|
||||
echo "测试数据集 API:"
|
||||
curl -s "${BASE_URL}:8001/api/datasets" | python3 -m json.tool 2>/dev/null || echo "请求失败"
|
||||
echo ""
|
||||
|
||||
echo "=================================="
|
||||
echo "3. 测试前端页面..."
|
||||
echo ""
|
||||
|
||||
# 测试前端页面
|
||||
echo "测试主页:"
|
||||
if curl -s -I "${BASE_URL}:8000/pages/main.html" | grep -q "200 OK"; then
|
||||
echo "✅ 主页可访问"
|
||||
else
|
||||
echo "❌ 主页无法访问"
|
||||
fi
|
||||
|
||||
echo "测试登录页:"
|
||||
if curl -s -I "${BASE_URL}:8000/pages/login.html" | grep -q "200 OK"; then
|
||||
echo "✅ 登录页可访问"
|
||||
else
|
||||
echo "❌ 登录页无法访问"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "✅ 测试完成!"
|
||||
echo ""
|
||||
echo "💡 如果服务未运行,请使用以下命令启动:"
|
||||
echo " ./total_start.sh"
|
||||
echo ""
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🧪 测试data目录功能"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
API_URL="http://10.10.10.77:8001/api"
|
||||
|
||||
echo "1. 测试获取文件列表..."
|
||||
curl -s "${API_URL}/datasets/files" | python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
print(f'✅ 共 {data[\"data\"][\"total\"]} 个文件')
|
||||
print('')
|
||||
for f in data['data']['files']:
|
||||
print(f' 📄 {f[\"filename\"]} - {f[\"size_human\"]}')
|
||||
"
|
||||
echo ""
|
||||
|
||||
echo "2. 测试上传新文件..."
|
||||
cat > /tmp/test_upload.json << 'INNER_EOF'
|
||||
[{"text": "测试上传", "label": "test"}]
|
||||
INNER_EOF
|
||||
|
||||
curl -s -X POST "${API_URL}/datasets/upload" \
|
||||
-F "file=@/tmp/test_upload.json" \
|
||||
-F "description=测试data目录上传" | python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
if data['code'] == 200:
|
||||
print('✅ 文件上传成功')
|
||||
print(f' 保存路径: {data[\"data\"][\"dataset\"][\"saved_path\"]}')
|
||||
else:
|
||||
print('❌ 上传失败')
|
||||
"
|
||||
echo ""
|
||||
|
||||
echo "3. 再次获取文件列表..."
|
||||
curl -s "${API_URL}/datasets/files" | python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
print(f'✅ 当前共 {data[\"data\"][\"total\"]} 个文件')
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "✅ 测试完成!"
|
||||
echo ""
|
||||
echo "📁 data目录位置:"
|
||||
echo " /data/code/FT_Platform/YG_FT_Platform/data/"
|
||||
echo ""
|
||||
echo "🔍 查看文件:"
|
||||
echo " ls -lh /data/code/FT_Platform/YG_FT_Platform/data/"
|
||||
200
total_start.sh
200
total_start.sh
@@ -1,200 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🚀 大模型微调平台 - 一键启动"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# 确保在正确的目录中
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "📂 当前目录: $SCRIPT_DIR"
|
||||
echo ""
|
||||
|
||||
# 获取本机IP地址
|
||||
SERVER_IP=$(hostname -I | awk '{print $1}')
|
||||
echo "🌐 本机 IP 地址: $SERVER_IP"
|
||||
echo ""
|
||||
|
||||
# 检查Python是否安装
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ 错误: Python3 未安装"
|
||||
echo "请先安装 Python3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "请选择启动方式:"
|
||||
echo "1) 启动所有服务(FastAPI + Web前端)"
|
||||
echo "2) 只启动 FastAPI 服务(端口 8001)"
|
||||
echo "3) 只启动 Web 前端服务(端口 8000)"
|
||||
echo "4) 交互式选择"
|
||||
echo ""
|
||||
|
||||
read -p "请输入选择 (1-4): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
echo ""
|
||||
echo "✅ 启动所有服务..."
|
||||
echo ""
|
||||
|
||||
# 检查并启动 FastAPI 服务
|
||||
echo "🔧 启动 FastAPI 服务..."
|
||||
cd "$SCRIPT_DIR/src"
|
||||
if [ ! -d "node_modules" ] 2>/dev/null && [ ! -f "main.py" ]; then
|
||||
echo "⚠️ 警告: 未找到 FastAPI 源码文件"
|
||||
fi
|
||||
|
||||
# 启动 FastAPI (后台运行)
|
||||
cd "$SCRIPT_DIR/src"
|
||||
python3 -m uvicorn main:app --host 0.0.0.0 --port 8001 &
|
||||
FASTAPI_PID=$!
|
||||
echo "✅ FastAPI 服务已启动 (PID: $FASTAPI_PID)"
|
||||
echo " API 地址: http://localhost:8001"
|
||||
echo " API 文档: http://localhost:8001/docs"
|
||||
echo ""
|
||||
|
||||
# 等待 FastAPI 启动
|
||||
sleep 2
|
||||
|
||||
# 启动 Web 前端服务
|
||||
echo "🌐 启动 Web 前端服务..."
|
||||
cd "$SCRIPT_DIR/web"
|
||||
python3 -m http.server 8000 &
|
||||
WEB_PID=$!
|
||||
echo "✅ Web 前端服务已启动 (PID: $WEB_PID)"
|
||||
echo " 前端地址: http://localhost:8000"
|
||||
echo ""
|
||||
|
||||
echo "=================================="
|
||||
echo "🎉 所有服务启动完成!"
|
||||
echo ""
|
||||
echo "📱 访问地址:"
|
||||
echo " - 前端页面: http://$SERVER_IP:8000/pages/main.html"
|
||||
echo " - 登录页面: http://$SERVER_IP:8000/pages/login.html"
|
||||
echo " - API 服务: http://$SERVER_IP:8001"
|
||||
echo " - API 文档: http://$SERVER_IP:8001/docs"
|
||||
echo ""
|
||||
echo "⚠️ 按 Ctrl+C 停止所有服务"
|
||||
echo ""
|
||||
|
||||
# 等待用户中断
|
||||
trap "echo ''; echo '🛑 正在停止服务...'; kill $FASTAPI_PID $WEB_PID 2>/dev/null; echo '✅ 所有服务已停止'; exit 0" INT
|
||||
|
||||
# 保持脚本运行
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
;;
|
||||
|
||||
2)
|
||||
echo ""
|
||||
echo "🔧 启动 FastAPI 服务..."
|
||||
cd "$SCRIPT_DIR/src"
|
||||
|
||||
# 检查源码文件是否存在
|
||||
if [ ! -f "main.py" ]; then
|
||||
echo "❌ 错误: 未找到 main.py 文件"
|
||||
echo "请确保在正确的目录中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ FastAPI 服务启动中..."
|
||||
echo " API 地址: http://localhost:8001"
|
||||
echo " API 文档: http://localhost:8001/docs"
|
||||
echo ""
|
||||
echo "⚠️ 按 Ctrl+C 停止服务"
|
||||
echo ""
|
||||
|
||||
python3 -m uvicorn main:app --host 0.0.0.0 --port 8001 --reload
|
||||
;;
|
||||
|
||||
3)
|
||||
echo ""
|
||||
echo "🌐 启动 Web 前端服务..."
|
||||
cd "$SCRIPT_DIR/web"
|
||||
|
||||
# 检查页面文件是否存在
|
||||
if [ ! -f "pages/main.html" ]; then
|
||||
echo "❌ 错误: 未找到 pages/main.html 文件"
|
||||
echo "请确保在正确的目录中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Web 前端服务启动中..."
|
||||
echo " 前端地址: http://localhost:8000"
|
||||
echo ""
|
||||
echo "⚠️ 按 Ctrl+C 停止服务"
|
||||
echo ""
|
||||
|
||||
python3 -m http.server 8000
|
||||
;;
|
||||
|
||||
4)
|
||||
echo ""
|
||||
echo "🔧 检查服务状态..."
|
||||
echo ""
|
||||
|
||||
# 检查 FastAPI
|
||||
if curl -s http://localhost:8001 > /dev/null 2>&1; then
|
||||
echo "✅ FastAPI 服务正在运行 (端口 8001)"
|
||||
else
|
||||
echo "❌ FastAPI 服务未运行 (端口 8001)"
|
||||
fi
|
||||
|
||||
# 检查 Web 服务
|
||||
if curl -s http://localhost:8000 > /dev/null 2>&1; then
|
||||
echo "✅ Web 前端服务正在运行 (端口 8000)"
|
||||
else
|
||||
echo "❌ Web 前端服务未运行 (端口 8000)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "是否启动 FastAPI 服务?(y/n): " start_fastapi
|
||||
if [[ $start_fastapi == "y" || $start_fastapi == "Y" ]]; then
|
||||
cd "$SCRIPT_DIR/src"
|
||||
python3 -m uvicorn main:app --host 0.0.0.0 --port 8001 &
|
||||
FASTAPI_PID=$!
|
||||
echo "✅ FastAPI 服务已启动 (PID: $FASTAPI_PID)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "是否启动 Web 前端服务?(y/n): " start_web
|
||||
if [[ $start_web == "y" || $start_web == "Y" ]]; then
|
||||
cd "$SCRIPT_DIR/web"
|
||||
python3 -m http.server 8000 &
|
||||
WEB_PID=$!
|
||||
echo "✅ Web 前端服务已启动 (PID: $WEB_PID)"
|
||||
fi
|
||||
|
||||
if [[ $start_fastapi == "y" || $start_fastapi == "Y" || $start_web == "y" || $start_web == "Y" ]]; then
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "🎉 服务启动完成!"
|
||||
echo ""
|
||||
echo "📱 访问地址:"
|
||||
echo " - 前端页面: http://$SERVER_IP:8000/pages/main.html"
|
||||
echo " - 登录页面: http://$SERVER_IP:8000/pages/login.html"
|
||||
echo " - API 服务: http://$SERVER_IP:8001"
|
||||
echo " - API 文档: http://$SERVER_IP:8001/docs"
|
||||
echo ""
|
||||
echo "⚠️ 按 Ctrl+C 停止服务"
|
||||
echo ""
|
||||
|
||||
# 等待用户中断
|
||||
trap "echo ''; echo '🛑 正在停止服务...'; kill $FASTAPI_PID $WEB_PID 2>/dev/null; echo '✅ 所有服务已停止'; exit 0" INT
|
||||
|
||||
# 保持脚本运行
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
else
|
||||
echo "未启动任何服务"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "❌ 无效选择,请运行脚本重新选择"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
455
web/pages/custom-tool-create.html
Normal file
455
web/pages/custom-tool-create.html
Normal file
@@ -0,0 +1,455 @@
|
||||
<!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;
|
||||
}
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.icon-option {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.icon-option:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
.icon-option.selected {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</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-deploy" data-page="model-deploy" 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 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=data-generate" 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>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<!-- 页面标题 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-b border-gray-100 mb-4">
|
||||
<div class="flex items-center text-sm">
|
||||
<span class="text-primary cursor-pointer hover:underline" onclick="window.location.href='main.html?page=data-generate'">其他工具</span>
|
||||
<span class="mx-2 text-gray-300">/</span>
|
||||
<span class="text-gray-800 font-medium" id="pageTitle">添加自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-6 max-w-2xl">
|
||||
<form id="toolForm">
|
||||
<!-- 工具名称 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>工具名称
|
||||
</label>
|
||||
<input type="text" name="name" class="form-input" placeholder="请输入工具名称" maxlength="30">
|
||||
<p class="text-xs text-gray-400 mt-1">支持中文、英文、数字,最多30个字符</p>
|
||||
</div>
|
||||
|
||||
<!-- 工具描述 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">工具描述</label>
|
||||
<textarea name="description" rows="3" class="form-input resize-none" placeholder="请输入工具描述" maxlength="100"></textarea>
|
||||
<p class="text-xs text-gray-400 mt-1"><span id="descCount">0</span> / 100</p>
|
||||
</div>
|
||||
|
||||
<!-- 跳转地址 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>跳转地址
|
||||
</label>
|
||||
<input type="text" name="url" class="form-input" placeholder="如:custom-tool.html 或 https://example.com">
|
||||
<p class="text-xs text-gray-400 mt-1">输入工具页面的相对路径或完整URL</p>
|
||||
</div>
|
||||
|
||||
<!-- 图标选择 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">选择图标</label>
|
||||
<div class="grid grid-cols-8 gap-2" id="iconGrid"></div>
|
||||
<input type="hidden" id="selectedIcon" value="fa-cog" name="icon">
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="flex items-center justify-between pt-6 border-t border-gray-100">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="submitForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90 transition-colors">
|
||||
<i class="fa fa-check mr-2"></i>保存
|
||||
</button>
|
||||
<a href="main.html?page=data-generate" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">
|
||||
<i class="fa fa-times mr-2"></i>取消
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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();
|
||||
|
||||
// 当前是否为编辑模式
|
||||
let isEditMode = false;
|
||||
let editToolId = null;
|
||||
|
||||
// 可选图标列表
|
||||
function getIconOptions() {
|
||||
return [
|
||||
'fa-cog', 'fa-cogs', 'fa-database', 'fa-code', 'fa-file-text',
|
||||
'fa-file-code', 'fa-file-excel', 'fa-file', 'fa-folder',
|
||||
'fa-wrench', 'fa-tools', 'fa-magic', 'fa-puzzle-piece',
|
||||
'fa-plug', 'fa-cube', 'fa-cubes', 'fa-gear', 'fa-sliders',
|
||||
'fa-filter', 'fa-refresh', 'fa-exchange', 'fa-arrows-alt',
|
||||
'fa-random', 'fa-random', 'fa-link', 'fa-chain',
|
||||
'fa-edit', 'fa-pencil', 'fa-pen', 'fa-plus-circle',
|
||||
'fa-star', 'fa-heart', 'fa-bookmark', 'fa-tag'
|
||||
];
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 检查是否为编辑模式
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
isEditMode = urlParams.get('edit') === 'true';
|
||||
|
||||
// 渲染图标选择
|
||||
renderIconGrid();
|
||||
|
||||
// 如果是编辑模式,加载工具数据
|
||||
if (isEditMode) {
|
||||
loadToolForEdit();
|
||||
}
|
||||
|
||||
// 描述字数统计
|
||||
const descInput = document.querySelector('textarea[name="description"]');
|
||||
if (descInput) {
|
||||
descInput.addEventListener('input', () => {
|
||||
document.getElementById('descCount').textContent = descInput.value.length;
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定导航点击事件
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!this.href.includes('custom-tool-create')) {
|
||||
e.preventDefault();
|
||||
window.location.href = this.href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 加载工具数据进行编辑
|
||||
function loadToolForEdit() {
|
||||
const editToolStr = localStorage.getItem('editTool');
|
||||
if (editToolStr) {
|
||||
const tool = JSON.parse(editToolStr);
|
||||
editToolId = tool.id;
|
||||
|
||||
// 更新页面标题
|
||||
document.getElementById('pageTitle').textContent = '修改自定义工具';
|
||||
document.title = '修改自定义工具 / 远光软件微调平台';
|
||||
|
||||
// 填充表单
|
||||
document.querySelector('input[name="name"]').value = tool.name || '';
|
||||
document.querySelector('textarea[name="description"]').value = tool.description || '';
|
||||
document.querySelector('input[name="url"]').value = tool.url || '';
|
||||
|
||||
// 选中图标
|
||||
const icon = tool.icon || 'fa-cog';
|
||||
setSelectedIcon(icon);
|
||||
|
||||
// 更新字数统计
|
||||
document.getElementById('descCount').textContent = (tool.description || '').length;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置选中的图标
|
||||
function setSelectedIcon(iconClass) {
|
||||
document.querySelectorAll('.icon-option').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
if (item.dataset.icon === iconClass) {
|
||||
item.classList.add('selected');
|
||||
}
|
||||
});
|
||||
document.querySelector('input[name="icon"]').value = iconClass;
|
||||
}
|
||||
|
||||
// 渲染图标选择网格
|
||||
function renderIconGrid() {
|
||||
const iconGrid = document.getElementById('iconGrid');
|
||||
const icons = getIconOptions();
|
||||
iconGrid.innerHTML = icons.map((icon, idx) => `
|
||||
<div class="icon-option ${idx === 0 ? 'selected' : ''}" data-icon="${icon}" onclick="selectIcon(this)">
|
||||
<i class="fa ${icon} text-lg text-gray-600"></i>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 选择图标
|
||||
function selectIcon(el) {
|
||||
document.querySelectorAll('.icon-option').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
});
|
||||
el.classList.add('selected');
|
||||
document.querySelector('input[name="icon"]').value = el.dataset.icon;
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
function submitForm() {
|
||||
const form = document.getElementById('toolForm');
|
||||
const formData = new FormData(form);
|
||||
const name = formData.get('name').trim();
|
||||
const url = formData.get('url').trim();
|
||||
const icon = formData.get('icon');
|
||||
const description = formData.get('description').trim();
|
||||
|
||||
if (!name) {
|
||||
showMessage('提示', '请输入工具名称', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!url) {
|
||||
showMessage('提示', '请输入跳转地址', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEditMode && editToolId) {
|
||||
// 编辑模式:更新现有工具
|
||||
updateCustomTool(editToolId, name, description, icon, url);
|
||||
} else {
|
||||
// 新增模式:创建新工具
|
||||
const toolId = 'custom_' + Date.now();
|
||||
const newTool = {
|
||||
id: toolId,
|
||||
name: name,
|
||||
description: description || '自定义工具',
|
||||
icon: icon,
|
||||
url: url
|
||||
};
|
||||
saveCustomTool(newTool);
|
||||
showMessage('成功', '自定义工具添加成功!', 'success', () => {
|
||||
window.location.href = 'main.html?page=data-generate';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义工具
|
||||
function updateCustomTool(toolId, name, description, icon, url) {
|
||||
let customTools = JSON.parse(localStorage.getItem('customTools') || '[]');
|
||||
const index = customTools.findIndex(t => t.id === toolId);
|
||||
if (index !== -1) {
|
||||
customTools[index] = {
|
||||
...customTools[index],
|
||||
name: name,
|
||||
description: description || '自定义工具',
|
||||
icon: icon,
|
||||
url: url
|
||||
};
|
||||
localStorage.setItem('customTools', JSON.stringify(customTools));
|
||||
// 清除编辑数据
|
||||
localStorage.removeItem('editTool');
|
||||
showMessage('成功', '自定义工具修改成功!', 'success', () => {
|
||||
window.location.href = 'main.html?page=data-generate';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 保存自定义工具
|
||||
function saveCustomTool(tool) {
|
||||
let customTools = JSON.parse(localStorage.getItem('customTools') || '[]');
|
||||
customTools.push(tool);
|
||||
localStorage.setItem('customTools', JSON.stringify(customTools));
|
||||
}
|
||||
|
||||
// ============ 自定义消息弹窗 ============
|
||||
function showMessage(title, message, type = 'info', onConfirm) {
|
||||
const modal = document.getElementById('customModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
const modalIcon = document.getElementById('modalIcon');
|
||||
const modalConfirmBtn = document.getElementById('modalConfirmBtn2');
|
||||
const modalBtnGroup = document.getElementById('modalBtnGroup');
|
||||
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
|
||||
|
||||
modalTitle.textContent = title;
|
||||
modalMessage.innerHTML = message;
|
||||
|
||||
if (type === 'success') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"><i class="fa fa-check text-xl text-green-600"></i></div>';
|
||||
} else if (type === 'error') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center"><i class="fa fa-times text-xl text-red-600"></i></div>';
|
||||
} else if (type === 'warning') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center"><i class="fa fa-exclamation text-xl text-yellow-600"></i></div>';
|
||||
} else {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-info text-xl text-blue-600"></i></div>';
|
||||
}
|
||||
|
||||
modalBtnGroup.classList.add('hidden');
|
||||
modalSingleBtnGroup.classList.remove('hidden');
|
||||
modalConfirmBtn.className = type === 'error' ? 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors' : 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
modalConfirmBtn.onclick = () => {
|
||||
modal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
if (onConfirm) onConfirm();
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 自定义消息弹窗 -->
|
||||
<div id="customModal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden">
|
||||
<div class="p-6 text-center">
|
||||
<div id="modalIcon"></div>
|
||||
<h3 id="modalTitle" class="text-lg font-medium text-gray-800 mb-2"></h3>
|
||||
<p id="modalMessage" class="text-gray-600 text-sm"></p>
|
||||
</div>
|
||||
<div id="modalBtnGroup" class="hidden px-6 pb-6">
|
||||
<button id="modalConfirmBtn" class="px-4 py-2 bg-primary text-white rounded-lg w-full hover:bg-primary/90 transition-colors">
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
<div id="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
|
||||
<button id="modalConfirmBtn2" class="px-6 py-2 text-white rounded-lg transition-colors">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
440
web/pages/dataset-create.html
Normal file
440
web/pages/dataset-create.html
Normal file
@@ -0,0 +1,440 @@
|
||||
<!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;
|
||||
}
|
||||
.form-select {
|
||||
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;
|
||||
appearance: none;
|
||||
background-color: white;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
.form-select:focus {
|
||||
border-color: #1890ff;
|
||||
outline: none;
|
||||
}
|
||||
.radio-dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.upload-area:hover,
|
||||
.upload-area.drag-over {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</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-deploy" data-page="model-deploy" 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 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="#" onclick="goBack()" 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>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<!-- 页面标题 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-b border-gray-100 mb-4">
|
||||
<div class="flex items-center text-sm">
|
||||
<span id="breadcrumbParent" class="text-primary cursor-pointer hover:underline" onclick="goBack()">数据集管理</span>
|
||||
<span class="mx-2 text-gray-300">/</span>
|
||||
<span class="text-gray-800 font-medium">上传数据集</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-6 max-w-3xl">
|
||||
<form id="datasetForm">
|
||||
<!-- 1. 数据集名称输入框 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">
|
||||
数据集名称
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="数据集名称"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none"
|
||||
maxlength="20"
|
||||
>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">0 / 20</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 数据集类型(单选按钮) -->
|
||||
<div class="mb-6 pl-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">数据集类型</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="dataset_type" id="train-set" value="train" checked onchange="switchDatasetType()" class="radio-custom absolute opacity-0">
|
||||
<div class="flex items-center space-x-1">
|
||||
<div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center">
|
||||
<div class="radio-dot"></div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">训练集</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="dataset_type" id="eval-set" value="eval" onchange="switchDatasetType()" class="radio-custom absolute opacity-0">
|
||||
<div class="flex items-center space-x-1">
|
||||
<div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center">
|
||||
<div class="radio-dot"></div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">评测集</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 存储位置 -->
|
||||
<div class="mb-6 pl-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">存储位置</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="storage" value="local" class="radio-custom absolute opacity-0" checked>
|
||||
<div class="flex items-center space-x-1">
|
||||
<div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center">
|
||||
<div class="radio-dot"></div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">本地存储</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="storage" value="cloud" class="radio-custom absolute opacity-0">
|
||||
<div class="flex items-center space-x-1">
|
||||
<div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center">
|
||||
<div class="radio-dot"></div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">云平台存储</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 上传文件区域 -->
|
||||
<div class="mb-6 pl-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">上传文件</label>
|
||||
<p class="text-xs text-gray-500 mb-2">选择文件进行上传,数据格式可下载模板查看,一次最多导入10个文件</p>
|
||||
<div
|
||||
id="upload-area"
|
||||
class="upload-area border-2 border-dashed border-gray-300 rounded-lg p-8 text-center transition-colors cursor-pointer relative"
|
||||
>
|
||||
<input type="file" id="file-upload" class="absolute opacity-0" multiple accept=".jsonl,.xls,.xlsx">
|
||||
<div class="flex flex-col items-center space-y-2">
|
||||
<i class="fa fa-cloud-upload text-2xl text-gray-400"></i>
|
||||
<p class="text-sm text-gray-600">点击或将文件拖拽到这里上传 (0/10)</p>
|
||||
<p class="text-xs text-gray-500">支持扩展名:jsonl, xls, xlsx, 文件最大200MB<br>一次最多导入10个文件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 8. 模板链接 -->
|
||||
<div class="mb-6 pl-4 space-x-4">
|
||||
<a href="#" class="text-primary text-sm hover:underline">
|
||||
<i class="fa fa-file-excel mr-1"></i>EXCEL数据模板
|
||||
</a>
|
||||
<a href="#" class="text-primary text-sm hover:underline">
|
||||
<i class="fa fa-file-code mr-1"></i>JSON数据模板
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="flex items-center justify-between pt-6 border-t border-gray-100 mt-8">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="submitForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90">
|
||||
保存
|
||||
</button>
|
||||
<a href="main.html?page=dataset-manage" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300">
|
||||
取消
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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();
|
||||
|
||||
// 返回页面
|
||||
let backUrl = 'main.html?page=dataset-manage';
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 根据URL参数设置返回页面
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const from = urlParams.get('from');
|
||||
const breadcrumbParent = document.getElementById('breadcrumbParent');
|
||||
if (from === 'fine-tune') {
|
||||
backUrl = 'fine-tune-create.html';
|
||||
if (breadcrumbParent) {
|
||||
breadcrumbParent.textContent = '创建训练任务';
|
||||
}
|
||||
}
|
||||
// 文件上传区域拖拽逻辑
|
||||
const uploadArea = document.getElementById('upload-area');
|
||||
const fileUpload = document.getElementById('file-upload');
|
||||
|
||||
// 点击上传区域触发文件选择
|
||||
uploadArea.addEventListener('click', () => fileUpload.click());
|
||||
|
||||
// 拖拽事件处理
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, () => uploadArea.classList.add('drag-over'), false);
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('drag-over'), false);
|
||||
});
|
||||
|
||||
// 处理文件拖放
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length) {
|
||||
fileUpload.files = files;
|
||||
console.log('拖放的文件:', files);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听文件选择
|
||||
fileUpload.addEventListener('change', () => {
|
||||
console.log('选择的文件:', fileUpload.files);
|
||||
});
|
||||
|
||||
// 绑定导航点击事件
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!this.href.includes('dataset-create')) {
|
||||
e.preventDefault();
|
||||
window.location.href = this.href;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化单选框选中样式
|
||||
initRadioStyles();
|
||||
});
|
||||
|
||||
// 初始化单选框选中样式
|
||||
function initRadioStyles() {
|
||||
document.querySelectorAll('.radio-custom').forEach(radio => {
|
||||
updateRadioStyle(radio);
|
||||
radio.addEventListener('change', function() {
|
||||
document.querySelectorAll('.radio-custom').forEach(r => updateRadioStyle(r));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 更新单选框样式
|
||||
function updateRadioStyle(radio) {
|
||||
const parent = radio.closest('label');
|
||||
const dotContainer = parent.querySelector('.w-4');
|
||||
const dot = parent.querySelector('.radio-dot');
|
||||
if (radio.checked) {
|
||||
if (dotContainer) {
|
||||
dotContainer.classList.add('border-primary', 'bg-primary/10');
|
||||
dotContainer.classList.remove('border-gray-300');
|
||||
}
|
||||
if (dot) {
|
||||
dot.classList.add('bg-primary');
|
||||
dot.classList.remove('bg-transparent');
|
||||
}
|
||||
} else {
|
||||
if (dotContainer) {
|
||||
dotContainer.classList.remove('border-primary', 'bg-primary/10');
|
||||
dotContainer.classList.add('border-gray-300');
|
||||
}
|
||||
if (dot) {
|
||||
dot.classList.remove('bg-primary');
|
||||
dot.classList.add('bg-transparent');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数据集类型切换逻辑
|
||||
function switchDatasetType() {
|
||||
// 数据集类型切换逻辑
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
const form = document.getElementById('datasetForm');
|
||||
const formData = new FormData(form);
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
dataset_type: formData.get('dataset_type'),
|
||||
storage: formData.get('storage')
|
||||
};
|
||||
|
||||
if (!data.name) {
|
||||
alert('请输入数据集名称');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/dataset-manage`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
alert('创建成功!');
|
||||
window.location.href = 'main.html?page=dataset-manage';
|
||||
} else {
|
||||
alert(result.message || '创建失败');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('创建失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
window.location.href = backUrl;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
787
web/pages/fine-tune-create.html
Normal file
787
web/pages/fine-tune-create.html
Normal file
@@ -0,0 +1,787 @@
|
||||
<!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;
|
||||
}
|
||||
.form-select {
|
||||
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;
|
||||
appearance: none;
|
||||
background-color: white;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
.form-select:focus {
|
||||
border-color: #1890ff;
|
||||
outline: none;
|
||||
}
|
||||
.card-radio {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.card-radio.active {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
.card-radio:hover {
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</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-deploy" data-page="model-deploy" 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 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" 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>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<!-- 页面标题 -->
|
||||
<div class="bg-white rounded-lg shadow-sm w-full p-4 border-b border-gray-100 mb-4">
|
||||
<div class="flex items-center text-sm">
|
||||
<span class="text-primary cursor-pointer hover:underline" onclick="window.location.href='main.html'">模型调优</span>
|
||||
<span class="mx-2 text-gray-300">/</span>
|
||||
<span class="text-gray-800 font-medium">创建训练任务</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<div class="bg-white rounded-lg shadow-sm w-full">
|
||||
<div class="p-6">
|
||||
<form id="createForm">
|
||||
<!-- 基本信息 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">基本信息</h3>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-gray-600 mb-3">任务名称</label>
|
||||
<div>
|
||||
<input type="text" name="name" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入任务名称" maxlength="50">
|
||||
<p class="text-xs text-gray-400 mt-1"><span id="nameCount">0</span> / 50</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 训练配置 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">训练配置</h3>
|
||||
|
||||
<!-- 训练方式 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">训练方式</label>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="card-radio active" data-value="SFT">
|
||||
<div class="flex items-start">
|
||||
<input type="radio" name="train_type" value="SFT" checked class="mt-1 mr-2">
|
||||
<div>
|
||||
<div class="font-medium text-sm">SFT 微调训练</div>
|
||||
<div class="text-xs text-gray-400 mt-1">在监督指令下,增强模型指令跟随的能力,提升全参数微调训练方式</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-radio" data-value="DPO">
|
||||
<div class="flex items-start">
|
||||
<input type="radio" name="train_type" value="DPO" class="mt-1 mr-2">
|
||||
<div>
|
||||
<div class="font-medium text-sm">DPO 偏好训练</div>
|
||||
<div class="text-xs text-gray-400 mt-1">引入人类反馈,降低幻觉,使得模型输出更符合人类偏好</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-radio" data-value="CPT">
|
||||
<div class="flex items-start">
|
||||
<input type="radio" name="train_type" value="CPT" class="mt-1 mr-2">
|
||||
<div>
|
||||
<div class="font-medium text-sm">CPT 继续预训练</div>
|
||||
<div class="text-xs text-gray-400 mt-1">通过无标注数据进行无监督继续训练,强化或新增模型特定能力</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选择模型 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">选择模型</label>
|
||||
<div class="flex items-center">
|
||||
<select name="base_model" id="baseModelSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择模型</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80" onclick="loadModels()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5" onclick="window.location.href='model-manage-create.html?from=fine-tune'">
|
||||
+ 新增模型
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 训练方法 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">训练方法</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="train_method" value="lora" class="mr-2" onchange="toggleTrainMethod()" checked>
|
||||
<span class="text-sm">高效训练</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="train_method" value="full" class="mr-2" onchange="toggleTrainMethod()">
|
||||
<span class="text-sm">全参训练</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 训练方法参数配置 -->
|
||||
<div id="trainMethodParams" class="mb-6">
|
||||
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
||||
<div class="flex items-center justify-between px-4 py-3 bg-gray-50 border-b border-gray-200">
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-sliders text-primary mr-2"></i>
|
||||
<label class="block text-sm font-medium text-gray-700">训练参数配置</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="resetParams()" class="text-gray-500 hover:text-primary text-sm flex items-center transition-colors" title="恢复默认配置">
|
||||
<i class="fa fa-rotate-left mr-1"></i>
|
||||
<span>重置</span>
|
||||
</button>
|
||||
<button type="button" onclick="toggleParamsCollapse()" class="text-gray-500 hover:text-primary text-sm flex items-center transition-colors">
|
||||
<span id="paramsToggleText">收起</span>
|
||||
<i id="paramsToggleIcon" class="fa fa-chevron-up ml-1 text-xs transition-transform"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="paramsContent" class="overflow-x-auto">
|
||||
<table class="w-full text-sm divide-y divide-gray-100">
|
||||
<thead class="bg-gradient-to-r from-blue-50 to-indigo-50">
|
||||
<tr>
|
||||
<th class="text-left py-3 px-4 font-medium text-gray-600">参数名称</th>
|
||||
<th class="text-left py-3 px-4 font-medium text-gray-600">参数值</th>
|
||||
<th class="text-left py-3 px-4 font-medium text-gray-600">取值范围</th>
|
||||
<th class="text-left py-3 px-4 font-medium text-gray-600">参数说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="commonParamsBody" class="divide-y divide-gray-100">
|
||||
<!-- 公共参数 -->
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">batch_size</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="batch_size" value="1" min="1" max="64" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[1, 64]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">单步训练样本数量,数值越大训练速度越快,但显存占用越高</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">learning_rate</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="learning_rate" value="0.0001" step="0.00001" min="0.000001" max="1" class="w-24 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[0.000001, 1]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">模型参数更新步长,过大可能导致训练不稳定,过小收敛速度慢</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">n_epochs</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="n_epochs" value="1" min="1" max="100" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[1, 100]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">完整遍历训练数据集的次数,建议设置在1-10之间</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">eval_steps</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="eval_steps" value="100" min="10" max="10000" class="w-24 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[10, 10000]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">每训练多少步进行一次模型评估,建议设置为100的倍数</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">lr_scheduler_type</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<select name="lr_scheduler_type" class="w-28 px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all bg-white">
|
||||
<option value="cosine">cosine</option>
|
||||
<option value="linear">linear</option>
|
||||
<option value="constant">constant</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">3种可选</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">学习率变化策略,cosine为余弦退火,linear为线性下降,constant为保持不变</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">max_length</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="max_length" value="512" min="64" max="4096" class="w-24 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[64, 4096]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">单条训练数据的最大token数,超出部分将被截断</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">warmup_ratio</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="warmup_ratio" value="0.05" step="0.01" min="0" max="1" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[0, 1]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">学习率预热步数占总步数的比例,设置为0则不预热</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/30 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">weight_decay</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="weight_decay" value="0.01" step="0.001" min="0" max="1" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[0, 1]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">防止过拟合的正则化技术,值越大对模型参数约束越强</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody id="loraParamsBody" class="hidden divide-y divide-gray-100">
|
||||
<!-- LoRA参数 -->
|
||||
<tr class="bg-blue-50/50 hover:bg-blue-50/70 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">lora_alpha</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<select name="lora_alpha" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all bg-white">
|
||||
<option value="16" selected>16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
<option value="128">128</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">4种可选</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">LoRA缩放系数,用于控制低秩适配矩阵的权重,影响模型对微调数据的敏感度</td>
|
||||
</tr>
|
||||
<tr class="bg-blue-50/50 hover:bg-blue-50/70 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">lora_dropout</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<input type="number" name="lora_dropout" value="0.1" step="0.01" min="0" max="1" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-center focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all">
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">[0, 1]</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">LoRA层 dropout 概率,在低秩适配矩阵中随机丢弃部分神经元以防止过拟合</td>
|
||||
</tr>
|
||||
<tr class="bg-blue-50/50 hover:bg-blue-50/70 transition-colors">
|
||||
<td class="py-3 px-4">
|
||||
<span class="text-gray-700 font-mono text-sm">lora_rank</span>
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<select name="lora_rank" class="w-20 px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all bg-white">
|
||||
<option value="8" selected>8</option>
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-mono">4种可选</span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-xs text-gray-500 leading-relaxed">LoRA低秩矩阵的秩,值越大表示低秩矩阵的维度越高,微调能力越强</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据配置 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">数据配置</h3>
|
||||
|
||||
<!-- 训练集 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-1">
|
||||
<span class="text-red-500 mr-1">*</span>训练集
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<select name="dataset_id" id="trainDatasetSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择训练数据集</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80" onclick="loadDatasets()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5" onclick="window.location.href='dataset-create.html?from=fine-tune'">
|
||||
+ 新增数据集
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 验证集 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">验证集 <span class="text-red-500">*</span></label>
|
||||
<div class="flex items-center space-x-6 mb-3">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="valid_split" value="auto" checked class="mr-2" onchange="toggleValidSplit()">
|
||||
<span class="text-sm">自动切分</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="valid_split" value="custom" class="mr-2" onchange="toggleValidSplit()">
|
||||
<span class="text-sm">选择数据集</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="autoSplitSection" class="flex items-center">
|
||||
<span class="text-sm text-gray-600 mr-2">从当前训练集随机分割</span>
|
||||
<input type="number" name="valid_ratio" value="10" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm text-center focus:border-primary focus:outline-none">
|
||||
<span class="text-sm text-gray-600 ml-2">% 作为验证集</span>
|
||||
</div>
|
||||
<div id="customSplitSection" class="hidden">
|
||||
<div class="flex items-center">
|
||||
<select name="valid_dataset_id" id="validDatasetSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择验证数据集</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80" onclick="loadDatasets()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5" onclick="window.location.href='dataset-create.html?from=fine-tune'">
|
||||
+ 新增数据集
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 训练产出 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">训练产出</h3>
|
||||
|
||||
<!-- 模型名称 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-gray-600 mb-3">模型名称</label>
|
||||
<div>
|
||||
<input type="text" name="output_model_name" class="w-64 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入模型名称" maxlength="50">
|
||||
<p class="text-xs text-gray-400 mt-1"><span id="modelNameCount">0</span> / 50</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型加密 -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm text-gray-600 mr-2">模型加密</span>
|
||||
<span class="px-2 py-0.5 bg-green-100 text-green-700 text-xs rounded">安全升级</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">为保障您的数据安全,平台会为导出的模型文件开启 OSS 服务端加密</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="flex items-center justify-between pt-6 border-t border-gray-100">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="submitForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90">
|
||||
开始训练
|
||||
</button>
|
||||
<a href="main.html" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300">
|
||||
取消
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center text-sm">
|
||||
<a href="#" class="text-primary hover:underline">训练费用 (预估)</a>
|
||||
<span class="mx-2 text-gray-300">|</span>
|
||||
<a href="#" class="text-primary hover:underline">计算详情</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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();
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 卡片式单选框
|
||||
document.querySelectorAll('.card-radio').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const parent = card.parentElement;
|
||||
parent.querySelectorAll('.card-radio').forEach(c => c.classList.remove('active'));
|
||||
card.classList.add('active');
|
||||
card.querySelector('input').checked = true;
|
||||
});
|
||||
});
|
||||
|
||||
// 任务名称字数统计
|
||||
const nameInput = document.querySelector('input[name="name"]');
|
||||
nameInput.addEventListener('input', () => {
|
||||
document.getElementById('nameCount').textContent = nameInput.value.length;
|
||||
});
|
||||
|
||||
// 模型名称字数统计
|
||||
const modelNameInput = document.querySelector('input[name="output_model_name"]');
|
||||
modelNameInput.addEventListener('input', () => {
|
||||
document.getElementById('modelNameCount').textContent = modelNameInput.value.length;
|
||||
});
|
||||
|
||||
// 加载数据集列表
|
||||
loadDatasets();
|
||||
|
||||
// 加载模型列表
|
||||
loadModels();
|
||||
|
||||
// 初始化训练方法参数显示
|
||||
toggleTrainMethod();
|
||||
|
||||
// 绑定导航点击事件
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!this.href.includes('fine-tune-create')) {
|
||||
e.preventDefault();
|
||||
window.location.href = this.href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 切换验证集切分方式
|
||||
function toggleValidSplit() {
|
||||
const validSplit = document.querySelector('input[name="valid_split"]:checked').value;
|
||||
const autoSection = document.getElementById('autoSplitSection');
|
||||
const customSection = document.getElementById('customSplitSection');
|
||||
if (validSplit === 'auto') {
|
||||
autoSection.classList.remove('hidden');
|
||||
customSection.classList.add('hidden');
|
||||
} else {
|
||||
autoSection.classList.add('hidden');
|
||||
customSection.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换训练方法 - 显示/隐藏LoRA参数
|
||||
function toggleTrainMethod() {
|
||||
const trainMethod = document.querySelector('input[name="train_method"]:checked').value;
|
||||
const loraParamsBody = document.getElementById('loraParamsBody');
|
||||
if (trainMethod === 'lora') {
|
||||
loraParamsBody.classList.remove('hidden');
|
||||
} else {
|
||||
loraParamsBody.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换参数配置展开/收起
|
||||
function toggleParamsCollapse() {
|
||||
const content = document.getElementById('paramsContent');
|
||||
const toggleText = document.getElementById('paramsToggleText');
|
||||
const toggleIcon = document.getElementById('paramsToggleIcon');
|
||||
|
||||
if (content.classList.contains('hidden')) {
|
||||
content.classList.remove('hidden');
|
||||
toggleText.textContent = '收起';
|
||||
toggleIcon.className = 'fa fa-chevron-up ml-1 text-xs';
|
||||
} else {
|
||||
content.classList.add('hidden');
|
||||
toggleText.textContent = '展开';
|
||||
toggleIcon.className = 'fa fa-chevron-down ml-1 text-xs';
|
||||
}
|
||||
}
|
||||
|
||||
// 重置参数为默认值
|
||||
function resetParams() {
|
||||
const defaults = {
|
||||
'batch_size': 1,
|
||||
'learning_rate': 0.0001,
|
||||
'n_epochs': 1,
|
||||
'eval_steps': 100,
|
||||
'lr_scheduler_type': 'cosine',
|
||||
'max_length': 512,
|
||||
'warmup_ratio': 0.05,
|
||||
'weight_decay': 0.01,
|
||||
'lora_alpha': '32',
|
||||
'lora_dropout': 0.1,
|
||||
'lora_rank': '8'
|
||||
};
|
||||
|
||||
for (const [name, value] of Object.entries(defaults)) {
|
||||
const input = document.querySelector(`input[name="${name}"]`);
|
||||
if (input) {
|
||||
if (input.type === 'number') {
|
||||
input.value = value;
|
||||
} else if (input.type === 'select-one') {
|
||||
input.value = value;
|
||||
}
|
||||
}
|
||||
const select = document.querySelector(`select[name="${name}"]`);
|
||||
if (select) select.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染数据集单选列表
|
||||
function renderDatasetRadio(datasets, containerId, name, selectedId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!datasets || datasets.length === 0) {
|
||||
container.innerHTML = '<span class="text-sm text-gray-400">暂无可用数据集</span>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = datasets.map(d => `
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<input type="radio" name="${name}" value="${d.id}" ${d.id === selectedId ? 'checked' : ''} class="text-primary focus:ring-primary">
|
||||
<span class="text-sm text-gray-700">${d.name}</span>
|
||||
</label>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 加载数据集列表
|
||||
async function loadDatasets() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/dataset-manage`);
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
// 更新训练集下拉框
|
||||
const trainSelect = document.getElementById('trainDatasetSelect');
|
||||
if (trainSelect) {
|
||||
trainSelect.innerHTML = '<option value="">请选择训练数据集</option>' +
|
||||
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
|
||||
}
|
||||
// 更新验证集下拉框
|
||||
const validSelect = document.getElementById('validDatasetSelect');
|
||||
if (validSelect) {
|
||||
validSelect.innerHTML = '<option value="">请选择验证数据集</option>' +
|
||||
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载数据集失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载模型列表
|
||||
async function loadModels() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage`);
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
const modelSelect = document.getElementById('baseModelSelect');
|
||||
if (modelSelect) {
|
||||
modelSelect.innerHTML = '<option value="">请选择模型</option>' +
|
||||
result.data.map(m => `<option value="${m.id}">${m.name}</option>`).join('');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载模型失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
const form = document.getElementById('createForm');
|
||||
const formData = new FormData(form);
|
||||
const validSplit = formData.get('valid_split');
|
||||
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
base_model: formData.get('base_model'),
|
||||
train_type: formData.get('train_type'),
|
||||
train_method: formData.get('train_method'),
|
||||
train_dataset_id: formData.get('train_dataset_id'),
|
||||
valid_split: validSplit,
|
||||
valid_ratio: parseInt(formData.get('valid_ratio')) || 10,
|
||||
valid_dataset_id: validSplit === 'custom' ? formData.get('valid_dataset_id') : null,
|
||||
output_model_name: formData.get('output_model_name'),
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
};
|
||||
|
||||
if (!data.name) {
|
||||
alert('请输入任务名称');
|
||||
return;
|
||||
}
|
||||
if (!data.base_model) {
|
||||
alert('请选择基础模型');
|
||||
return;
|
||||
}
|
||||
if (!data.train_dataset_id) {
|
||||
alert('请选择训练集');
|
||||
return;
|
||||
}
|
||||
if (validSplit === 'custom' && !data.valid_dataset_id) {
|
||||
alert('请选择验证集');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/fine-tune`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
alert('创建成功!');
|
||||
window.location.href = 'main.html';
|
||||
} else {
|
||||
alert(result.message || '创建失败');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('创建失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,83 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login Page</title>
|
||||
<link rel="stylesheet" href="../css/login.css">
|
||||
<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>
|
||||
.login-card-shadow {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.input-focus {
|
||||
transition: border-color 0.2s, outline 0.2s;
|
||||
}
|
||||
.input-focus:focus {
|
||||
border-color: #1890ff;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
.text-primary { color: #1890ff; }
|
||||
.text-danger { color: #f5222d; }
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.bg-danger\/5 { background-color: rgba(245, 34, 45, 0.05); }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="svg-top">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1337" width="1337">
|
||||
<defs>
|
||||
<path id="path-1" opacity="1" fill-rule="evenodd" d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"/>
|
||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
||||
<stop offset="0" stop-color="rgb(88,62,213)" stop-opacity="1"/>
|
||||
<stop offset="1" stop-color="rgb(23,215,250)" stop-opacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<body class="login-page antialiased bg-gray-50 min-h-screen flex items-center justify-center p-4" style="background-color: #DBE0F9;">
|
||||
<!-- 登录主容器 -->
|
||||
<div class="w-full max-w-md">
|
||||
<!-- 登录卡片 -->
|
||||
<div class="bg-white rounded-xl p-8 login-card-shadow">
|
||||
<!-- 平台LOGO和标题 -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 mb-4">
|
||||
<img src="../assets/logo/logo.png" alt="Logo" class="w-12 h-12 object-contain">
|
||||
</div>
|
||||
<h1 class="text-[clamp(1.5rem,3vw,2rem)] font-medium text-gray-800 mb-1">远光软件微调平台</h1>
|
||||
<p class="text-gray-500">模型管理平台</p>
|
||||
</div>
|
||||
|
||||
<div class="svg-bottom">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="896" width="967.8852157128662">
|
||||
<defs>
|
||||
<path id="path-2" opacity="1" fill-rule="evenodd" d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"/>
|
||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
||||
<stop offset="0" stop-color="rgb(40,175,240)" stop-opacity="1"/>
|
||||
<stop offset="1" stop-color="rgb(18,15,196)" stop-opacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- 登录表单 -->
|
||||
<form id="loginForm" class="space-y-5">
|
||||
<!-- 账号输入框 -->
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<i class="fa fa-user-o mr-1 text-gray-400"></i>账号
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<i class="fa fa-user-o"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
class="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg input-focus transition-colors"
|
||||
placeholder="请输入账号/手机号/邮箱"
|
||||
value="admin"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="container">
|
||||
<section class="wrapper">
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img src="../assets/logo/logo.png" alt="Logo">
|
||||
<!-- 密码输入框 -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700">
|
||||
<i class="fa fa-lock-o mr-1 text-gray-400"></i>密码
|
||||
</label>
|
||||
<a href="#" class="text-xs text-primary hover:text-primary/80 transition-colors">忘记密码?</a>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<i class="fa fa-lock-o"></i>
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="w-full pl-10 pr-10 py-2.5 border border-gray-300 rounded-lg input-focus transition-colors"
|
||||
placeholder="请输入密码"
|
||||
value="admin"
|
||||
required
|
||||
>
|
||||
<button type="button" id="togglePassword" class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fa fa-eye-slash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记住密码 & 验证码登录 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center text-sm text-gray-600 cursor-pointer">
|
||||
<input type="checkbox" class="w-4 h-4 text-primary rounded border-gray-300 focus:ring-primary">
|
||||
<span class="ml-2">记住密码</span>
|
||||
</label>
|
||||
<a href="#" class="text-xs text-primary hover:text-primary/80 transition-colors">验证码登录</a>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div id="errorMsg" class="hidden text-danger text-sm bg-danger/5 p-2 rounded-lg">
|
||||
<i class="fa fa-exclamation-circle mr-1"></i>
|
||||
<span>账号或密码错误,请重新输入</span>
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-primary text-white py-2.5 rounded-lg hover:bg-primary/90 active:bg-primary/95 transition-colors font-medium flex items-center justify-center"
|
||||
>
|
||||
<i class="fa fa-sign-in mr-2"></i>登 录
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<h1>欢迎回来!</h1>
|
||||
<p>用户登录</p>
|
||||
</header>
|
||||
<section class="main-content">
|
||||
<form id="loginForm" onsubmit="handleLogin(event)">
|
||||
<input type="text" placeholder="用户名" id="username">
|
||||
<div class="line"></div>
|
||||
<input type="password" placeholder="密码" id="password">
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<p><a href="" title="忘记密码">忘记密码?</a></p>
|
||||
<p><a href="" title="注册">注册</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function handleLogin(event) {
|
||||
event.preventDefault();
|
||||
<!-- 简单的交互脚本 -->
|
||||
<script>
|
||||
// 动态获取 API 基础地址(根据当前访问的 IP 自动调整)
|
||||
const getApiBase = () => {
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
return `${protocol}//${hostname}:8080/api`;
|
||||
};
|
||||
const API_BASE = getApiBase();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
// 密码显示/隐藏切换
|
||||
const togglePassword = document.getElementById('togglePassword');
|
||||
const password = document.getElementById('password');
|
||||
|
||||
// 简单的验证:用户名和密码都不为空
|
||||
if (username && password) {
|
||||
// 登录成功,设置登录状态
|
||||
sessionStorage.setItem('isLoggedIn', 'true');
|
||||
// 跳转到主页
|
||||
window.location.href = 'main.html';
|
||||
} else {
|
||||
alert('请输入用户名和密码!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
togglePassword.addEventListener('click', () => {
|
||||
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
password.setAttribute('type', type);
|
||||
// 切换图标
|
||||
togglePassword.innerHTML = type === 'password' ? '<i class="fa fa-eye-slash"></i>' : '<i class="fa fa-eye"></i>';
|
||||
});
|
||||
|
||||
// 表单提交处理
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const errorMsg = document.getElementById('errorMsg');
|
||||
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const passwordVal = document.getElementById('password').value;
|
||||
|
||||
if (!username || !passwordVal) {
|
||||
errorMsg.textContent = '账号和密码不能为空';
|
||||
errorMsg.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password: passwordVal })
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
errorMsg.classList.add('hidden');
|
||||
window.location.href = 'main.html';
|
||||
} else {
|
||||
errorMsg.textContent = result.message || '账号或密码错误';
|
||||
errorMsg.classList.remove('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
errorMsg.textContent = '无法连接到服务器,请确保后端服务已启动';
|
||||
errorMsg.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
2573
web/pages/main.html
2573
web/pages/main.html
File diff suppressed because it is too large
Load Diff
465
web/pages/model-eval-create.html
Normal file
465
web/pages/model-eval-create.html
Normal file
@@ -0,0 +1,465 @@
|
||||
<!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;
|
||||
}
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.form-select {
|
||||
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;
|
||||
appearance: none;
|
||||
background-color: white;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
.form-select:focus {
|
||||
border-color: #1890ff;
|
||||
outline: none;
|
||||
}
|
||||
.card-radio {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.card-radio.active {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</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-deploy" data-page="model-deploy" 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 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-eval" 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>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<!-- 页面标题 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-b border-gray-100 mb-4">
|
||||
<div class="flex items-center text-sm">
|
||||
<span class="text-primary cursor-pointer hover:underline" onclick="window.location.href='main.html?page=model-eval'">模型评测</span>
|
||||
<span class="mx-2 text-gray-300">/</span>
|
||||
<span class="text-gray-800 font-medium">新建评测</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-6">
|
||||
<form id="evalForm">
|
||||
<!-- 任务名称 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>任务名称
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<input type="text" name="name" id="taskName" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入任务名称" maxlength="50">
|
||||
<span class="text-xs text-gray-400 ml-2"><span id="nameCount">0</span> / 50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评测方式 -->
|
||||
<div class="mb-6">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>评测方式
|
||||
</label>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<label class="border-2 border-primary bg-blue-50 rounded-lg p-4 cursor-pointer" id="customEvalLabel">
|
||||
<input type="radio" name="eval_type" value="custom" checked class="text-primary focus:ring-primary sr-only" onchange="toggleEvalType('custom')">
|
||||
<div class="font-medium text-gray-800">自定义评测</div>
|
||||
<p class="text-xs text-gray-500 mt-1">支持基于AI评测、自动化指标计算、人工评测方式<br>自定义评测维度,可自由组合上述各类维度进行评测。</p>
|
||||
</label>
|
||||
<label class="border border-gray-200 rounded-lg p-4 cursor-pointer hover:border-primary/50" id="baselineEvalLabel">
|
||||
<input type="radio" name="eval_type" value="baseline" class="text-primary focus:ring-primary sr-only" onchange="toggleEvalType('baseline')">
|
||||
<div class="font-medium text-gray-800">基线评测</div>
|
||||
<p class="text-xs text-gray-500 mt-1">通过预置基线评测集,评测模型基本能力,自动生成评测报告</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评测对象 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">评测对象</h3>
|
||||
|
||||
<div class="max-w-md">
|
||||
<!-- 评测模型 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-gray-600 mb-1">
|
||||
<span class="text-red-500 mr-1">*</span>评测模型
|
||||
</label>
|
||||
<select name="model_id" id="modelSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 数据来源 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-gray-600 mb-2">
|
||||
<span class="text-red-500 mr-1">*</span>数据来源
|
||||
</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="data_source" value="dataset" class="mr-2" onchange="toggleDataSource('dataset')">
|
||||
<span class="text-sm text-gray-700">评测数据集</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="data_source" value="inference" checked class="mr-2" onchange="toggleDataSource('inference')">
|
||||
<span class="text-sm text-gray-700">推理结果集</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推理结果集上传 -->
|
||||
<div id="inferenceUpload" class="border-2 border-dashed border-gray-200 rounded-lg p-8 text-center hover:border-primary/50 transition-colors cursor-pointer">
|
||||
<i class="fa fa-cloud-upload text-gray-400 text-xl mb-2"></i>
|
||||
<p class="text-gray-700 text-sm">点击或拖拽上传推理结果集</p>
|
||||
<p class="text-xs text-gray-400 mt-1">支持 .xls .xlsx 格式,不超过2MB</p>
|
||||
<a href="#" class="text-primary text-xs mt-2 inline-block hover:underline">下载模板</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评测数据集选择 -->
|
||||
<div id="datasetSelect" class="hidden mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">评测数据集</h3>
|
||||
<div class="flex items-center">
|
||||
<select name="dataset_id" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择评测数据集</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5" onclick="window.location.href='dataset-create.html'">
|
||||
+ 新增数据集
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评测规则 -->
|
||||
<div class="mb-6" id="evalRulesSection">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">评测规则</h3>
|
||||
<div class="flex items-center">
|
||||
<select name="eval_dimension" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择评测维度</option>
|
||||
<option value="accuracy">准确率</option>
|
||||
<option value="recall">召回率</option>
|
||||
<option value="f1">F1值</option>
|
||||
<option value="bleu">BLEU</option>
|
||||
<option value="rouge">ROUGE</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5">
|
||||
+ 创建评测维度
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leaderboard -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">Leaderboard</h3>
|
||||
<div class="flex items-center">
|
||||
<label class="text-gray-700 mr-4">是否参与排行</label>
|
||||
<div class="relative inline-block w-10 h-5">
|
||||
<input type="checkbox" name="leaderboard" class="opacity-0 w-0 h-0" onchange="toggleLeaderboard(this)">
|
||||
<div class="bg-gray-200 rounded-full absolute inset-0 peer-focus:ring-2 peer-focus:ring-primary/30"></div>
|
||||
<div class="bg-white w-4 h-4 rounded-full absolute left-0.5 top-0.5 transition-transform" id="leaderboardToggle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div class="flex justify-between items-center pt-6 border-t border-gray-100">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="submitForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90">
|
||||
开始评测
|
||||
</button>
|
||||
<a href="main.html?page=model-eval" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300">
|
||||
取消
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-gray-400 text-xs">
|
||||
费用 以实际发生为准 <a href="#" class="text-primary hover:underline">计算详情</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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();
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 任务名称字数统计
|
||||
const nameInput = document.getElementById('taskName');
|
||||
nameInput.addEventListener('input', () => {
|
||||
document.getElementById('nameCount').textContent = nameInput.value.length;
|
||||
});
|
||||
|
||||
// 初始化评测方式样式
|
||||
updateEvalTypeStyle('custom');
|
||||
|
||||
// 加载模型列表
|
||||
loadModels();
|
||||
|
||||
// 绑定导航点击事件
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!this.href.includes('model-eval-create')) {
|
||||
e.preventDefault();
|
||||
window.location.href = this.href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 切换评测方式
|
||||
function toggleEvalType(type) {
|
||||
updateEvalTypeStyle(type);
|
||||
const rulesSection = document.getElementById('evalRulesSection');
|
||||
if (type === 'baseline') {
|
||||
rulesSection.classList.add('hidden');
|
||||
} else {
|
||||
rulesSection.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function updateEvalTypeStyle(type) {
|
||||
const customLabel = document.getElementById('customEvalLabel');
|
||||
const baselineLabel = document.getElementById('baselineEvalLabel');
|
||||
|
||||
if (type === 'custom') {
|
||||
customLabel.classList.add('border-primary', 'bg-blue-50');
|
||||
customLabel.classList.remove('border-gray-200');
|
||||
baselineLabel.classList.remove('border-primary', 'bg-blue-50');
|
||||
baselineLabel.classList.add('border-gray-200');
|
||||
} else {
|
||||
baselineLabel.classList.add('border-primary', 'bg-blue-50');
|
||||
baselineLabel.classList.remove('border-gray-200');
|
||||
customLabel.classList.remove('border-primary', 'bg-blue-50');
|
||||
customLabel.classList.add('border-gray-200');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换数据来源
|
||||
function toggleDataSource(source) {
|
||||
const inferenceUpload = document.getElementById('inferenceUpload');
|
||||
const datasetSelect = document.getElementById('datasetSelect');
|
||||
|
||||
if (source === 'inference') {
|
||||
inferenceUpload.classList.remove('hidden');
|
||||
datasetSelect.classList.add('hidden');
|
||||
} else {
|
||||
inferenceUpload.classList.add('hidden');
|
||||
datasetSelect.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换Leaderboard开关样式
|
||||
function toggleLeaderboard(checkbox) {
|
||||
const toggle = document.getElementById('leaderboardToggle');
|
||||
if (checkbox.checked) {
|
||||
toggle.style.transform = 'translateX(20px)';
|
||||
toggle.previousElementSibling.classList.remove('bg-gray-200');
|
||||
toggle.previousElementSibling.classList.add('bg-primary');
|
||||
} else {
|
||||
toggle.style.transform = 'translateX(0)';
|
||||
toggle.previousElementSibling.classList.remove('bg-primary');
|
||||
toggle.previousElementSibling.classList.add('bg-gray-200');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载模型列表
|
||||
async function loadModels() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/my-models`);
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
const select = document.getElementById('modelSelect');
|
||||
select.innerHTML = '<option value="">请选择</option>' +
|
||||
result.data.map(m => `<option value="${m.id}">${m.name}</option>`).join('');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载模型失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
const form = document.getElementById('evalForm');
|
||||
const formData = new FormData(form);
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
eval_type: formData.get('eval_type'),
|
||||
model_id: formData.get('model_id'),
|
||||
data_source: formData.get('data_source'),
|
||||
leaderboard: formData.get('leader') === 'on'
|
||||
};
|
||||
|
||||
if (!data.name) {
|
||||
alert('请输入任务名称');
|
||||
return;
|
||||
}
|
||||
if (!data.model_id) {
|
||||
alert('请选择评测模型');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-eval`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
alert('创建成功!');
|
||||
window.location.href = 'main.html?page=model-eval';
|
||||
} else {
|
||||
alert(result.message || '创建失败');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('创建失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
467
web/pages/model-manage-create.html
Normal file
467
web/pages/model-manage-create.html
Normal file
@@ -0,0 +1,467 @@
|
||||
<!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;
|
||||
}
|
||||
.form-select {
|
||||
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;
|
||||
appearance: none;
|
||||
background-color: white;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
.form-select:focus {
|
||||
border-color: #1890ff;
|
||||
outline: none;
|
||||
}
|
||||
.upload-area:hover,
|
||||
.upload-area.drag-over {
|
||||
border-color: #1890ff;
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
|
||||
</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-deploy" data-page="model-deploy" 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 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="#" onclick="goBack()" 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>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<!-- 大标题 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-b border-gray-100">
|
||||
<div class="flex items-center text-sm">
|
||||
<span id="breadcrumbParent" class="text-primary cursor-pointer hover:underline" onclick="goBack()">模型管理</span>
|
||||
<span class="mx-2 text-gray-300">/</span>
|
||||
<span class="text-gray-800 font-medium">添加模型</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-6">
|
||||
<form id="modelForm">
|
||||
<!-- 基本信息 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">基本信息</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型名称
|
||||
</label>
|
||||
<input type="text" name="name" class="form-input" placeholder="请输入模型名称" maxlength="100">
|
||||
<p class="text-xs text-gray-400 mt-1">支持中文、英文、数字、下划线,最多100个字符</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型类型
|
||||
</label>
|
||||
<select name="type" class="form-input">
|
||||
<option value="">请选择</option>
|
||||
<option value="LLM">大语言模型 (LLM)</option>
|
||||
<option value="CV">计算机视觉 (CV)</option>
|
||||
<option value="NLP">自然语言处理 (NLP)</option>
|
||||
<option value="Embedding">向量模型 (Embedding)</option>
|
||||
<option value="Other">其他</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">版本信息</h3>
|
||||
<div class="max-w-md">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型版本
|
||||
</label>
|
||||
<input type="text" name="version" class="form-input" placeholder="如:v1.0.0" maxlength="50">
|
||||
<p class="text-xs text-gray-400 mt-1">建议使用语义化版本号,如:v1.0.0、v2.1.0</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型路径 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">模型路径</h3>
|
||||
|
||||
<!-- 模型来源选择 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-gray-600 mb-3">模型来源</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="model_source" value="local" checked class="mr-2" onchange="toggleModelSource('local')">
|
||||
<span class="text-sm">本地模型</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="model_source" value="online" class="mr-2" onchange="toggleModelSource('online')">
|
||||
<span class="text-sm">在线模型</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本地模型配置 -->
|
||||
<div id="localModelPanel" class="max-w-xl">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型加载路径
|
||||
</label>
|
||||
<input type="text" name="local_path" class="form-input" placeholder="如:/models/my-model 或 s3://bucket/models/my-model">
|
||||
<p class="text-xs text-gray-400 mt-1">支持本地路径或云存储路径(OSS、S3、HDFS等)</p>
|
||||
</div>
|
||||
|
||||
<!-- 在线模型配置 -->
|
||||
<div id="onlineModelPanel" class="hidden max-w-xl">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>API 地址
|
||||
</label>
|
||||
<input type="text" name="api_url" class="form-input" placeholder="如:https://api.openai.com/v1">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>API Key
|
||||
</label>
|
||||
<input type="password" name="api_key" class="form-input" placeholder="请输入API Key">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型名称
|
||||
</label>
|
||||
<input type="text" name="online_model_name" class="form-input" placeholder="如:gpt-4、qwen-turbo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 描述信息 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">描述信息</h3>
|
||||
<div>
|
||||
<label class="form-label">模型描述</label>
|
||||
<textarea name="description" rows="4" class="form-input resize-none" placeholder="请输入模型描述,包含模型特点、适用场景等信息" maxlength="500"></textarea>
|
||||
<p class="text-xs text-gray-400 mt-1"><span id="descCount">0</span> / 500</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="flex items-center justify-between pt-6 border-t border-gray-100">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button type="button" onclick="submitForm()" 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-check mr-2"></i>保存
|
||||
</button>
|
||||
<a href="main.html?page=model-manage" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors flex items-center">
|
||||
<i class="fa fa-times mr-2"></i>取消
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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();
|
||||
|
||||
// 返回页面
|
||||
let backUrl = 'main.html?page=model-manage';
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 根据URL参数设置返回页面
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const from = urlParams.get('from');
|
||||
const breadcrumbParent = document.getElementById('breadcrumbParent');
|
||||
if (from === 'fine-tune') {
|
||||
backUrl = 'fine-tune-create.html';
|
||||
if (breadcrumbParent) {
|
||||
breadcrumbParent.textContent = '创建训练任务';
|
||||
}
|
||||
}
|
||||
// 描述字数统计
|
||||
const descInput = document.querySelector('textarea[name="description"]');
|
||||
if (descInput) {
|
||||
descInput.addEventListener('input', () => {
|
||||
document.getElementById('descCount').textContent = descInput.value.length;
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定导航点击事件
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!this.href.includes('model-manage-create')) {
|
||||
e.preventDefault();
|
||||
window.location.href = this.href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 切换模型来源
|
||||
function toggleModelSource(source) {
|
||||
const localPanel = document.getElementById('localModelPanel');
|
||||
const onlinePanel = document.getElementById('onlineModelPanel');
|
||||
if (source === 'local') {
|
||||
localPanel.classList.remove('hidden');
|
||||
onlinePanel.classList.add('hidden');
|
||||
} else {
|
||||
localPanel.classList.add('hidden');
|
||||
onlinePanel.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
const form = document.getElementById('modelForm');
|
||||
const formData = new FormData(form);
|
||||
const modelSource = formData.get('model_source');
|
||||
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
type: formData.get('type'),
|
||||
version: formData.get('version'),
|
||||
model_source: modelSource,
|
||||
description: formData.get('description')
|
||||
};
|
||||
|
||||
// 根据模型来源设置不同的字段
|
||||
if (modelSource === 'local') {
|
||||
data.path = formData.get('local_path');
|
||||
if (!data.path) {
|
||||
showMessage('提示', '请输入模型加载路径', 'warning');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.api_url = formData.get('api_url');
|
||||
data.api_key = formData.get('api_key');
|
||||
data.model_name = formData.get('online_model_name');
|
||||
if (!data.api_url) {
|
||||
showMessage('提示', '请输入API地址', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.api_key) {
|
||||
showMessage('提示', '请输入API Key', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.model_name) {
|
||||
showMessage('提示', '请输入模型名称', 'warning');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证
|
||||
if (!data.name) {
|
||||
showMessage('提示', '请输入模型名称', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.type) {
|
||||
showMessage('提示', '请选择模型类型', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.version) {
|
||||
showMessage('提示', '请输入模型版本', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
showMessage('成功', '模型添加成功!', 'success', () => {
|
||||
window.location.href = 'main.html?page=model-manage';
|
||||
});
|
||||
} else {
|
||||
showMessage('错误', result.message || '添加失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('错误', '添加失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 自定义消息弹窗 ============
|
||||
function showMessage(title, message, type = 'info', onConfirm) {
|
||||
const modal = document.getElementById('customModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
const modalIcon = document.getElementById('modalIcon');
|
||||
const modalConfirmBtn = document.getElementById('modalConfirmBtn2');
|
||||
const modalBtnGroup = document.getElementById('modalBtnGroup');
|
||||
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
|
||||
|
||||
modalTitle.textContent = title;
|
||||
modalMessage.innerHTML = message;
|
||||
|
||||
if (type === 'success') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"><i class="fa fa-check text-xl text-green-600"></i></div>';
|
||||
} else if (type === 'error') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center"><i class="fa fa-times text-xl text-red-600"></i></div>';
|
||||
} else if (type === 'warning') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center"><i class="fa fa-exclamation text-xl text-yellow-600"></i></div>';
|
||||
} else {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-info text-xl text-blue-600"></i></div>';
|
||||
}
|
||||
|
||||
modalBtnGroup.classList.add('hidden');
|
||||
modalSingleBtnGroup.classList.remove('hidden');
|
||||
modalConfirmBtn.className = type === 'error' ? 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors' : 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
modalConfirmBtn.onclick = () => {
|
||||
modal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
if (onConfirm) onConfirm();
|
||||
};
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
window.location.href = backUrl;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 自定义消息弹窗 -->
|
||||
<div id="customModal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden">
|
||||
<div class="p-6 text-center">
|
||||
<div id="modalIcon"></div>
|
||||
<h3 id="modalTitle" class="text-lg font-medium text-gray-800 mb-2"></h3>
|
||||
<p id="modalMessage" class="text-gray-600 text-sm"></p>
|
||||
</div>
|
||||
<div id="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
|
||||
<button id="modalConfirmBtn2" class="px-6 py-2 text-white rounded-lg transition-colors">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
16
web/run_web.sh
Normal file
16
web/run_web.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# 启动前端 Web 服务器
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "启动前端 Web 服务..."
|
||||
echo "访问地址: http://localhost:8081"
|
||||
echo ""
|
||||
echo "请确保后端服务也在运行 (在另一个终端执行):"
|
||||
echo " cd /data/code/FT_Platform/YG_FT_Platform/src"
|
||||
echo " pip install -r ../requirements.txt"
|
||||
echo " python3 main.py"
|
||||
echo ""
|
||||
|
||||
# 使用 Python 内置 HTTP 服务器
|
||||
python3 -m http.server 8081
|
||||
Reference in New Issue
Block a user