feat: 搭建应用基础框架
包含应用入口、根组件、路由配置(含登录守卫)、主布局、全局样式、ECharts 插件、类型定义、常量映射表及静态资源。
This commit is contained in:
7
frontend/src/App.vue
Normal file
7
frontend/src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// 根组件,仅承载路由出口
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
106
frontend/src/constants/dimension.ts
Normal file
106
frontend/src/constants/dimension.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { DimensionType } from '@/types'
|
||||
|
||||
/** 各指标类型的评估方法选项 */
|
||||
export const EVAL_METHODS: Record<
|
||||
DimensionType | string,
|
||||
{ value: string; name: string; desc: string }[]
|
||||
> = {
|
||||
classification: [
|
||||
{ value: 'standard', name: '标准匹配', desc: '根据预设标准评估' },
|
||||
{ value: 'sentiment', name: '情感分析', desc: '识别文本情感倾向' },
|
||||
{ value: 'custom', name: '自定义评分器', desc: '自定义评估规则' },
|
||||
],
|
||||
metric: [
|
||||
{ value: 'metric_standard', name: '综合评测', desc: '根据预设标准评分' },
|
||||
{ value: 'semantic', name: '语义相似度', desc: '计算语义相似程度' },
|
||||
{ value: 'custom', name: '自定义评分器', desc: '自定义评估规则' },
|
||||
],
|
||||
text_similarity: [
|
||||
{ value: 'bleu_4', name: 'BLEU-4', desc: 'N-gram 精确度评估' },
|
||||
{ value: 'cosine', name: 'Cosine', desc: '余弦相似度' },
|
||||
{ value: 'rouge_1', name: 'ROUGE-1', desc: 'unigram 重叠评估' },
|
||||
{ value: 'rouge_2', name: 'ROUGE-2', desc: 'bigram 重叠评估' },
|
||||
{ value: 'rouge_4', name: 'ROUGE-4', desc: '4-gram 重叠评估' },
|
||||
],
|
||||
}
|
||||
|
||||
/** 各评估方法的预置 Prompt 模板 */
|
||||
export const EVAL_METHOD_PROMPTS: Record<string, string> = {
|
||||
standard: `# 角色
|
||||
你是一位专业的对话评估专家,擅长根据提供的标准对助理在对话中的最终反应进行评估,并确定其是否[[Pass]]或[[Fail]]。
|
||||
|
||||
## 技能
|
||||
### 技能 1: 对话回顾
|
||||
- 通读整个对话以理解上下文和背景信息。
|
||||
- 确保全面理解对话的意图和用户的需求。
|
||||
|
||||
### 技能 2: 最终回答识别
|
||||
- 从对话中准确识别出助理给出的最后一个回答。
|
||||
- 确保关注的是最终的回答,而不是中间的部分。
|
||||
|
||||
### 技能 3: 标准应用
|
||||
- 仔细审查每个评价标准。
|
||||
- 将助理的最终回答与标准的各个方面进行详细比较。
|
||||
|
||||
### 技能 4: 逐步推理
|
||||
- 记录每个标准以及最终响应如何满足或不满足该标准。
|
||||
- 提供详细的证据,并解释为什么最终答复满足或不满足期望。
|
||||
|
||||
### 技能 5: 结果判定
|
||||
- 根据逐步推理的结果,判断最终回答是[[Pass]]或[[Fail]]
|
||||
- 提供明确的结论并解释理由。
|
||||
|
||||
## 输出格式
|
||||
以下列格式提供结果:
|
||||
- **分步推理:**[详细推理在这里]
|
||||
- **最终结果:**[[Pass]]或[[Fail]]
|
||||
|
||||
## 限制
|
||||
- 只针对对话中的最终回答进行评估。
|
||||
- 在评估过程中,确保遵循提供的标准,避免主观判断。`,
|
||||
|
||||
sentiment: `# 角色
|
||||
你是一位专业的情感分析专家,擅长识别文本中的情感倾向,并给出[[Positive]]、[[Neutral]]或[[Negative]]的判断。
|
||||
|
||||
## 技能
|
||||
### 技能 1: 文本理解
|
||||
- 通读输入文本,理解其内容和语境。
|
||||
|
||||
### 技能 2: 情感识别
|
||||
- 识别文本中表达的情感词汇、语气和情感强度。
|
||||
- 分析上下文中的情感线索。
|
||||
|
||||
### 技能 3: 情感判定
|
||||
- 根据识别到的情感信息,判断整体情感倾向。
|
||||
|
||||
## 输出格式
|
||||
- **情感分析:**[分析过程]
|
||||
- **最终结果:**[[Positive]]/[[Neutral]]/[[Negative]]`,
|
||||
|
||||
metric_standard: `# 角色
|
||||
你是一位专业的模型评估专家,擅长根据预设标准对模型输出进行综合评分(0-5分)。
|
||||
|
||||
## 评估标准
|
||||
请从以下维度对模型输出进行评分:
|
||||
1. 准确性:回答是否准确无误
|
||||
2. 完整性:是否完整覆盖了问题的各个方面
|
||||
3. 连贯性:回答是否逻辑清晰、结构合理
|
||||
4. 可读性:语言表达是否流畅、易懂
|
||||
|
||||
## 输出格式
|
||||
- **评分理由:**[详细说明]
|
||||
- **最终评分:**[0-5 的分数]`,
|
||||
|
||||
semantic: `# 角色
|
||||
你是一位专业的语义相似度评估专家,擅长计算两段文本之间的语义相似程度。
|
||||
|
||||
## 任务
|
||||
请评估生成文本与参考文本在语义上的相似程度,给出 0-1 之间的分数。
|
||||
|
||||
## 输出格式
|
||||
- **相似度分析:**[分析过程]
|
||||
- **相似度分数:**[0-1 的分数]`,
|
||||
|
||||
custom: `# 自定义评估器
|
||||
请在此编写您的自定义评估 Prompt...`,
|
||||
}
|
||||
144
frontend/src/constants/index.ts
Normal file
144
frontend/src/constants/index.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 全局常量与映射表
|
||||
*/
|
||||
|
||||
/** 模型类型 */
|
||||
export const MODEL_TYPE_MAP: Record<string, string> = {
|
||||
LLM: '大语言模型',
|
||||
CV: '计算机视觉',
|
||||
NLP: '自然语言处理',
|
||||
Embedding: '向量模型',
|
||||
Other: '其他',
|
||||
}
|
||||
|
||||
/** 模型用途 */
|
||||
export const PURPOSE_MAP: Record<string, { text: string; type: string }> = {
|
||||
training: { text: '训练', type: 'primary' },
|
||||
inference: { text: '推理', type: 'success' },
|
||||
evaluation: { text: '评测', type: 'warning' },
|
||||
}
|
||||
|
||||
/** 模型来源 */
|
||||
export const MODEL_SOURCE_MAP: Record<string, string> = {
|
||||
local: '本地模型',
|
||||
api: '在线模型',
|
||||
online: '在线模型',
|
||||
}
|
||||
|
||||
/** 训练方式 */
|
||||
export const TRAIN_TYPE_MAP: Record<string, string> = {
|
||||
SFT: 'SFT',
|
||||
DPO: 'DPO',
|
||||
CPT: 'CPT',
|
||||
}
|
||||
|
||||
/** 训练方法 */
|
||||
export const TRAIN_METHOD_MAP: Record<string, string> = {
|
||||
lora: 'LoRA',
|
||||
qlora: 'QLoRA',
|
||||
full: '全量微调',
|
||||
prefix: 'Prefix Tuning',
|
||||
adapter: 'Adapter',
|
||||
peft: 'PEFT',
|
||||
adalora: 'AdaLoRA',
|
||||
longlora: 'LongLoRA',
|
||||
dpo: 'DPO',
|
||||
cpt: 'CPT',
|
||||
}
|
||||
|
||||
/** 数据集类型 */
|
||||
export const DATASET_TYPE_MAP: Record<string, string> = {
|
||||
train: '训练数据',
|
||||
test: '测试数据',
|
||||
eval: '评测数据',
|
||||
val: '验证数据',
|
||||
other: '其他',
|
||||
}
|
||||
|
||||
/** 存储位置 */
|
||||
export const STORAGE_MAP: Record<string, string> = {
|
||||
local: '本地存储',
|
||||
minio: 'MinIO',
|
||||
cloud: '云存储',
|
||||
}
|
||||
|
||||
/** 评测维度指标类型 */
|
||||
export const DIMENSION_TYPE_MAP: Record<string, string> = {
|
||||
classification: '大模型-分类型',
|
||||
metric: '大模型-指标型',
|
||||
text_similarity: '规则-文本相似度',
|
||||
}
|
||||
|
||||
/** 学习率调度器类型 */
|
||||
export const LR_SCHEDULER_OPTIONS = [
|
||||
{ label: 'cosine', value: 'cosine' },
|
||||
{ label: 'linear', value: 'linear' },
|
||||
{ label: 'constant', value: 'constant' },
|
||||
]
|
||||
|
||||
/** 训练模板分组(按模型系列) */
|
||||
export const TEMPLATE_GROUPS: { label: string; options: { label: string; value: string }[] }[] = [
|
||||
{
|
||||
label: 'Qwen 系列',
|
||||
options: [
|
||||
{ label: 'qwen', value: 'qwen' },
|
||||
{ label: 'qwen2', value: 'qwen2' },
|
||||
{ label: 'qwen2_5', value: 'qwen2_5' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Llama 系列',
|
||||
options: [
|
||||
{ label: 'llama2', value: 'llama2' },
|
||||
{ label: 'llama3', value: 'llama3' },
|
||||
{ label: 'llama3_1', value: 'llama3_1' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'DeepSeek 系列',
|
||||
options: [
|
||||
{ label: 'deepseek', value: 'deepseek' },
|
||||
{ label: 'deepseek2', value: 'deepseek2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '其他系列',
|
||||
options: [
|
||||
{ label: 'glm4', value: 'glm4' },
|
||||
{ label: 'chatglm3', value: 'chatglm3' },
|
||||
{ label: 'gemma', value: 'gemma' },
|
||||
{ label: 'gemma2', value: 'gemma2' },
|
||||
{ label: 'phi', value: 'phi' },
|
||||
{ label: 'intern2', value: 'intern2' },
|
||||
{ label: 'mistral', value: 'mistral' },
|
||||
{ label: 'default', value: 'default' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/** 默认工具列表(其他工具页) */
|
||||
export const DEFAULT_TOOLS = [
|
||||
{ id: 'data-generate', name: '数据生成', icon: 'fa-database', description: '基于LLM生成微调数据集' },
|
||||
{ id: 'json2jsonl', name: 'JSON转JSONL', icon: 'fa-code', description: '将JSON文件转换为JSONL格式' },
|
||||
{ id: 'md-convert', name: '转换Markdown', icon: 'fa-file-text', description: '将Markdown文件转换为训练数据' },
|
||||
]
|
||||
|
||||
/** 自定义工具可选图标 */
|
||||
export const TOOL_ICONS = [
|
||||
'fa-cog', 'fa-database', 'fa-code', 'fa-file-text', 'fa-wrench', 'fa-cube',
|
||||
'fa-line-chart', 'fa-bar-chart', 'fa-server', 'fa-cogs', 'fa-tasks', 'fa-trophy',
|
||||
'fa-sliders', 'fa-rocket', 'fa-cloud', 'fa-cloud-download', 'fa-microchip',
|
||||
'fa-hdd-o', 'fa-globe', 'fa-info-circle', 'fa-question-circle-o', 'fa-user-o',
|
||||
'fa-lock-o', 'fa-sign-in', 'fa-sign-out', 'fa-search', 'fa-plus', 'fa-refresh',
|
||||
'fa-times', 'fa-check', 'fa-pencil', 'fa-trash', 'fa-play', 'fa-arrow-left',
|
||||
'fa-arrow-down', 'fa-arrow-up',
|
||||
]
|
||||
|
||||
/** 会话超时:5 分钟 */
|
||||
export const SESSION_TIMEOUT = 5 * 60 * 1000
|
||||
|
||||
/** 系统监控刷新间隔 */
|
||||
export const METRICS_INTERVAL = 30000
|
||||
|
||||
/** 训练进度刷新间隔 */
|
||||
export const TRAINING_REFRESH_INTERVAL = 5000
|
||||
116
frontend/src/layouts/MainLayout.vue
Normal file
116
frontend/src/layouts/MainLayout.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import AppSidebar from '@/components/AppSidebar.vue'
|
||||
import AppHeader from '@/components/AppHeader.vue'
|
||||
import { useSystemStore } from '@/stores/system'
|
||||
import { useModelsStore } from '@/stores/models'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
const modelsStore = useModelsStore()
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(() => {
|
||||
// 启动顶部栏系统监控轮询
|
||||
systemStore.start()
|
||||
// 预加载模型列表缓存(列表页渲染模型名用)
|
||||
modelsStore.load()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
systemStore.stop()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main-layout">
|
||||
<AppSidebar />
|
||||
<div class="layout-main">
|
||||
<AppHeader />
|
||||
<main class="layout-content">
|
||||
<div
|
||||
class="page-canvas"
|
||||
:class="{ 'is-self-surface': route.meta.pageSurface === 'self' }"
|
||||
>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main-layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 8px 16px;
|
||||
background-color: var(--app-shell-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
&:has(.data-table-page),
|
||||
&:has(.eval-page),
|
||||
&:has(.manage-card-container) {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
&:has(.create-wizard-layout) {
|
||||
overflow-y: hidden;
|
||||
|
||||
.page-canvas {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-canvas {
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--app-page-bg);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.page-canvas.is-self-surface {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.page-canvas:has(.has-fixed-footer) {
|
||||
margin-bottom: 56px;
|
||||
}
|
||||
|
||||
/* 路由直接输出 PageCard,或显式声明根卡片宿主时,复用外层白色画布 */
|
||||
.page-canvas:not(.is-self-surface) > :deep(.page-card),
|
||||
.page-canvas:not(.is-self-surface) > :deep(.page-card-host > .page-card) {
|
||||
margin-bottom: 0;
|
||||
background-color: transparent;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
25
frontend/src/main.ts
Normal file
25
frontend/src/main.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
// Font Awesome 图标
|
||||
import '@/assets/font-awesome/css/font-awesome.min.css'
|
||||
|
||||
// ECharts(按需引入)+ vue-echarts 组件
|
||||
import '@/plugins/echarts'
|
||||
import VChart from 'vue-echarts'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './styles/index.scss'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
app.component('VChart', VChart)
|
||||
|
||||
app.mount('#app')
|
||||
24
frontend/src/plugins/echarts.ts
Normal file
24
frontend/src/plugins/echarts.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* ECharts 按需引入
|
||||
* 仅注册训练曲线所需的模块,避免引入全量包以减小体积
|
||||
*/
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { LineChart } from 'echarts/charts'
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
MarkLineComponent,
|
||||
} from 'echarts/components'
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
LineChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
MarkLineComponent,
|
||||
])
|
||||
239
frontend/src/router/index.ts
Normal file
239
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/LoginView.vue'),
|
||||
meta: { title: '登录', public: true },
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('@/layouts/MainLayout.vue'),
|
||||
redirect: '/fine-tune',
|
||||
children: [
|
||||
// 服务看板
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'dashboard',
|
||||
component: () => import('@/components/PlaceholderView.vue'),
|
||||
meta: { title: '服务看板' },
|
||||
},
|
||||
// 模型调优
|
||||
{
|
||||
path: 'fine-tune',
|
||||
name: 'fine-tune',
|
||||
component: () => import('@/views/fine-tune/FineTuneListView.vue'),
|
||||
meta: { title: '模型微调', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'fine-tune/create',
|
||||
name: 'fine-tune-create',
|
||||
component: () => import('@/views/fine-tune/FineTuneCreateView.vue'),
|
||||
meta: { title: '创建训练任务' },
|
||||
},
|
||||
// 模型评测
|
||||
{
|
||||
path: 'model-eval',
|
||||
name: 'model-eval',
|
||||
component: () => import('@/views/eval/EvalView.vue'),
|
||||
meta: { title: '模型评测', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'model-eval/create',
|
||||
name: 'model-eval-create',
|
||||
component: () => import('@/views/eval/EvalCreateView.vue'),
|
||||
meta: { title: '新建评测' },
|
||||
},
|
||||
{
|
||||
path: 'model-eval/dimension/create',
|
||||
name: 'dimension-create',
|
||||
component: () => import('@/views/eval/DimensionCreateView.vue'),
|
||||
meta: { title: '添加评测维度' },
|
||||
},
|
||||
{
|
||||
path: 'model-eval/dimension/:id/edit',
|
||||
name: 'dimension-edit',
|
||||
component: () => import('@/views/eval/DimensionCreateView.vue'),
|
||||
meta: { title: '编辑评测维度' },
|
||||
},
|
||||
// 模型推理
|
||||
{
|
||||
path: 'model-inference',
|
||||
name: 'model-inference',
|
||||
component: () => import('@/views/inference/InferenceListView.vue'),
|
||||
meta: { title: '模型推理', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'model-inference/create',
|
||||
name: 'model-inference-create',
|
||||
component: () => import('@/views/inference/InferenceCreateView.vue'),
|
||||
meta: { title: '新建推理' },
|
||||
},
|
||||
{
|
||||
path: 'model-inference/chat/:id',
|
||||
name: 'inference-chat',
|
||||
component: () => import('@/views/inference/InferenceChatView.vue'),
|
||||
meta: { title: '模型对话', pageSurface: 'self' },
|
||||
},
|
||||
// 模型对比
|
||||
{
|
||||
path: 'model-compare/chat/:id',
|
||||
name: 'compare-chat',
|
||||
component: () => import('@/views/compare/CompareChatView.vue'),
|
||||
meta: { title: '模型对比' },
|
||||
},
|
||||
{
|
||||
path: 'model-compare/result',
|
||||
name: 'compare-result',
|
||||
component: () => import('@/views/compare/CompareResultView.vue'),
|
||||
meta: { title: '对比结果' },
|
||||
},
|
||||
// 模型管理
|
||||
{
|
||||
path: 'model-manage',
|
||||
name: 'model-manage',
|
||||
component: () => import('@/views/model/ModelManageView.vue'),
|
||||
meta: { title: '模型管理', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'model-manage/create',
|
||||
name: 'model-manage-create',
|
||||
component: () => import('@/views/model/ModelCreateView.vue'),
|
||||
meta: { title: '添加模型' },
|
||||
},
|
||||
{
|
||||
path: 'model-manage/:id/edit',
|
||||
name: 'model-manage-edit',
|
||||
component: () => import('@/views/model/ModelCreateView.vue'),
|
||||
meta: { title: '编辑模型' },
|
||||
},
|
||||
{
|
||||
path: 'model-manage/merge',
|
||||
name: 'merge-weights',
|
||||
component: () => import('@/views/model/MergeWeightsView.vue'),
|
||||
meta: { title: '合并权重' },
|
||||
},
|
||||
// 数据处理
|
||||
{
|
||||
path: 'data-process',
|
||||
name: 'data-process',
|
||||
component: () => import('@/views/data-process/DataProcessListView.vue'),
|
||||
meta: { title: '数据处理', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'data-process/create',
|
||||
name: 'data-process-create',
|
||||
component: () => import('@/views/data-process/DataProcessCreateView.vue'),
|
||||
meta: { title: '新建数据处理任务', pageSurface: 'self' },
|
||||
},
|
||||
// 数据集管理
|
||||
{
|
||||
path: 'dataset',
|
||||
name: 'dataset',
|
||||
component: () => import('@/views/dataset/DatasetListView.vue'),
|
||||
meta: { title: '数据集管理', pageSurface: 'self' },
|
||||
},
|
||||
{
|
||||
path: 'dataset/create',
|
||||
name: 'dataset-create',
|
||||
component: () => import('@/views/dataset/DatasetCreateView.vue'),
|
||||
meta: { title: '上传数据集' },
|
||||
},
|
||||
{
|
||||
path: 'dataset/:id/edit',
|
||||
name: 'dataset-edit',
|
||||
component: () => import('@/views/dataset/DatasetCreateView.vue'),
|
||||
meta: { title: '编辑数据集' },
|
||||
},
|
||||
{
|
||||
path: 'dataset/:id/preview',
|
||||
name: 'dataset-preview',
|
||||
component: () => import('@/views/dataset/DatasetPreviewView.vue'),
|
||||
meta: { title: '数据集预览' },
|
||||
},
|
||||
// 其他工具
|
||||
{
|
||||
path: 'tools',
|
||||
name: 'tools',
|
||||
component: () => import('@/views/tools/ToolsView.vue'),
|
||||
meta: { title: '其他工具' },
|
||||
},
|
||||
{
|
||||
path: 'tools/create',
|
||||
name: 'tools-create',
|
||||
component: () => import('@/views/tools/ToolCreateView.vue'),
|
||||
meta: { title: '添加自定义工具' },
|
||||
},
|
||||
{
|
||||
path: 'tools/:id/edit',
|
||||
name: 'tools-edit',
|
||||
component: () => import('@/views/tools/ToolCreateView.vue'),
|
||||
meta: { title: '编辑自定义工具' },
|
||||
},
|
||||
// 数据类型转换
|
||||
{
|
||||
path: 'data-convert',
|
||||
name: 'data-convert',
|
||||
component: () => import('@/components/PlaceholderView.vue'),
|
||||
meta: { title: '数据类型转换' },
|
||||
},
|
||||
// 系统设置
|
||||
{
|
||||
path: 'hardware',
|
||||
name: 'hardware',
|
||||
component: () => import('@/views/system/HardwareView.vue'),
|
||||
meta: { title: '平台性能' },
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
name: 'logs',
|
||||
component: () => import('@/views/system/LogsView.vue'),
|
||||
meta: { title: '查看日志' },
|
||||
},
|
||||
{
|
||||
path: 'training-log/:id',
|
||||
name: 'training-log',
|
||||
component: () => import('@/views/system/TrainingLogView.vue'),
|
||||
meta: { title: '训练日志' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/fine-tune',
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
// 全局守卫:登录校验 + 会话超时
|
||||
router.beforeEach((to, _from, next) => {
|
||||
const auth = useAuthStore()
|
||||
document.title = to.meta.title ? `${to.meta.title} - 远光软件微调平台` : '远光软件微调平台'
|
||||
|
||||
if (to.meta.public) {
|
||||
// 已登录访问登录页则跳转主页
|
||||
if (to.name === 'login' && auth.isLoggedIn) {
|
||||
next('/fine-tune')
|
||||
return
|
||||
}
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!auth.isLoggedIn) {
|
||||
next({ name: 'login' })
|
||||
return
|
||||
}
|
||||
|
||||
// 续期会话
|
||||
auth.refresh()
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
190
frontend/src/styles/index.scss
Normal file
190
frontend/src/styles/index.scss
Normal file
@@ -0,0 +1,190 @@
|
||||
/* Element Plus 主题变量覆盖:使用原生 CSS 变量替代 SCSS 编译以解决加载卡顿和 FOUC 问题 */
|
||||
:root {
|
||||
/* 全局页面层级:灰色仅作为外层留白,业务页面统一使用白色画布 */
|
||||
--app-shell-bg: #f3f5f8;
|
||||
--app-page-bg: #ffffff;
|
||||
--app-surface-bg: #ffffff;
|
||||
--el-bg-color-page: var(--app-shell-bg);
|
||||
|
||||
/* Element Plus 基础颜色覆盖 */
|
||||
--el-color-primary: #4f46e5;
|
||||
--el-color-primary-light-3: #818cf8;
|
||||
--el-color-primary-light-5: #a5b4fc;
|
||||
--el-color-primary-light-7: #c7d2fe;
|
||||
--el-color-primary-light-8: #e0e7ff;
|
||||
--el-color-primary-light-9: #eef2ff;
|
||||
--el-color-primary-dark-2: #4338ca;
|
||||
|
||||
--el-color-success: #10b981;
|
||||
--el-color-warning: #f59e0b;
|
||||
--el-color-danger: #ef4444;
|
||||
--el-color-info: #64748b;
|
||||
|
||||
/* Element Plus 圆角覆盖 */
|
||||
--el-border-radius-base: 8px;
|
||||
--el-border-radius-small: 6px;
|
||||
--el-border-radius-round: 20px;
|
||||
|
||||
/* 项目自定义 CSS 变量 */
|
||||
--primary-color: var(--el-color-primary);
|
||||
--primary-hover: var(--el-color-primary-light-3);
|
||||
--danger-color: var(--el-color-danger);
|
||||
--success-color: var(--el-color-success);
|
||||
--sidebar-bg: #f9fafb; /* Light Gray */
|
||||
--sidebar-text: #606266; /* Regular Text */
|
||||
--sidebar-active-bg: #f1f5f9; /* Active Item Bg */
|
||||
--sidebar-active-text: #303133; /* Darker Text for Active */
|
||||
--card-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
|
||||
--header-height: 60px;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'PingFang SC', sans-serif;
|
||||
background-color: var(--app-shell-bg);
|
||||
color: var(--el-text-color-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 滚动条美化 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(148, 163, 184, 0.4);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(148, 163, 184, 0.7);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 覆盖 Element Plus 的一些默认样式,使其更高级 */
|
||||
|
||||
/* 1. 卡片 (el-card) 无边框,柔和阴影 */
|
||||
.el-card {
|
||||
background-color: var(--app-surface-bg);
|
||||
border: none !important;
|
||||
box-shadow: var(--card-shadow) !important;
|
||||
border-radius: 12px !important;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
/* 2. 表格 (el-table) 现代化 */
|
||||
.el-table {
|
||||
--el-table-border-color: #f1f5f9;
|
||||
--el-table-header-bg-color: #f8fafc;
|
||||
--el-table-header-text-color: #475569;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-table th.el-table__cell {
|
||||
font-weight: 600 !important;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--el-table-border-color) !important;
|
||||
}
|
||||
.el-table td.el-table__cell {
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
|
||||
background-color: #f8fafc !important;
|
||||
}
|
||||
.el-table::before {
|
||||
display: none; /* 移除底部基础边框 */
|
||||
}
|
||||
|
||||
/* 3. 按钮动效 */
|
||||
.el-button {
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.el-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.el-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.el-button.is-link:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 4. 标签 (el-tag) 圆润化 */
|
||||
.el-tag {
|
||||
border-radius: 6px;
|
||||
border: none !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
.el-tag--primary {
|
||||
background-color: rgba(79, 70, 229, 0.1);
|
||||
color: #4f46e5;
|
||||
}
|
||||
.el-tag--success {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
}
|
||||
.el-tag--warning {
|
||||
background-color: rgba(245, 158, 11, 0.1);
|
||||
color: #d97706;
|
||||
}
|
||||
.el-tag--danger {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
.el-tag--info {
|
||||
background-color: rgba(100, 116, 139, 0.1);
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
/* 5. 分页组件 */
|
||||
.el-pagination {
|
||||
--el-pagination-bg-color: #ffffff;
|
||||
--el-pagination-hover-color: var(--primary-color);
|
||||
margin-top: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.el-pagination.is-background .el-pager li {
|
||||
border-radius: 6px;
|
||||
background-color: #f1f5f9;
|
||||
border: none;
|
||||
}
|
||||
.el-pagination.is-background .el-pager li.is-active {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
/* 6. 模态框 (Dialog) */
|
||||
.el-dialog {
|
||||
border-radius: 16px !important;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
.el-dialog__header {
|
||||
margin-right: 0 !important;
|
||||
padding-bottom: 16px !important;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
.el-dialog__title {
|
||||
font-weight: 600 !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
260
frontend/src/types/index.ts
Normal file
260
frontend/src/types/index.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 全局业务类型定义
|
||||
*/
|
||||
|
||||
/** 通用分页/列表响应暂用数组,后端未分页 */
|
||||
|
||||
// ============ 模型管理 ============
|
||||
export type ModelType = 'LLM' | 'CV' | 'NLP' | 'Embedding' | 'Other'
|
||||
export type ModelPurpose = 'training' | 'inference' | 'evaluation'
|
||||
export type ModelSource = 'local' | 'api' | 'online'
|
||||
|
||||
export interface TrainMethod {
|
||||
name: string
|
||||
}
|
||||
|
||||
/** 数据库配置模型 */
|
||||
export interface ModelItem {
|
||||
id: number | string
|
||||
name: string
|
||||
type: ModelType | string
|
||||
purpose: ModelPurpose | string
|
||||
model_source: ModelSource | string
|
||||
description?: string
|
||||
path?: string
|
||||
api_url?: string
|
||||
api_key?: string
|
||||
online_model_name?: string
|
||||
create_time?: string
|
||||
}
|
||||
|
||||
/** 已训练模型 */
|
||||
export interface TrainedModel {
|
||||
id: number | string
|
||||
name: string
|
||||
train_methods?: TrainMethod[]
|
||||
base_model_path?: string
|
||||
create_time?: string
|
||||
merged?: boolean
|
||||
merging?: boolean
|
||||
merged_path?: string
|
||||
}
|
||||
|
||||
/** 创建/编辑模型请求体 */
|
||||
export interface ModelForm {
|
||||
id?: number | string
|
||||
name: string
|
||||
type: ModelType | string
|
||||
purpose: ModelPurpose | string
|
||||
model_source: ModelSource | string
|
||||
description?: string
|
||||
path?: string
|
||||
api_url?: string
|
||||
api_key?: string
|
||||
online_model_name?: string
|
||||
}
|
||||
|
||||
// ============ 数据集 ============
|
||||
export type DatasetType = 'train' | 'test' | 'eval' | 'val' | 'other'
|
||||
export type DatasetStorage = 'local' | 'cloud' | 'minio'
|
||||
|
||||
export interface DatasetFile {
|
||||
id?: string
|
||||
name: string
|
||||
size?: string | number
|
||||
[k: string]: any
|
||||
}
|
||||
|
||||
export interface DatasetItem {
|
||||
id: number | string
|
||||
name: string
|
||||
type: DatasetType | string
|
||||
storage_type: DatasetStorage | string
|
||||
size?: string | number
|
||||
count?: number
|
||||
description?: string
|
||||
create_time?: string
|
||||
files?: DatasetFile[]
|
||||
}
|
||||
|
||||
// ============ 训练任务 ============
|
||||
export type TrainType = 'SFT' | 'DPO' | 'CPT'
|
||||
export type TrainMethodType = 'lora' | 'full'
|
||||
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | string
|
||||
|
||||
export interface FineTuneTask {
|
||||
id: number | string
|
||||
name: string
|
||||
description?: string
|
||||
status: TaskStatus
|
||||
train_type: TrainType | string
|
||||
train_method?: TrainMethodType | string
|
||||
template?: string
|
||||
base_model: number | string
|
||||
train_dataset_id?: number | string
|
||||
output_model_name?: string
|
||||
gpus?: number[]
|
||||
batch_size?: number
|
||||
learning_rate?: number
|
||||
n_epochs?: number
|
||||
save_steps?: number
|
||||
lr_scheduler_type?: string
|
||||
max_length?: number
|
||||
warmup_ratio?: number
|
||||
weight_decay?: number
|
||||
lora_alpha?: number
|
||||
lora_dropout?: number
|
||||
lora_rank?: number
|
||||
progress?: number
|
||||
process_id?: number
|
||||
train_duration?: string
|
||||
create_time?: string
|
||||
}
|
||||
|
||||
export interface TrainingProgress {
|
||||
status?: string
|
||||
progress?: number
|
||||
step?: string
|
||||
speed?: string
|
||||
eta?: string
|
||||
}
|
||||
|
||||
// ============ 模型推理 / 对比 (model-compare) ============
|
||||
export interface LoadedModel {
|
||||
model_id?: number | string
|
||||
model_name?: string
|
||||
status?: string
|
||||
pid?: number
|
||||
port?: number
|
||||
}
|
||||
|
||||
export interface CompareTask {
|
||||
id: number | string
|
||||
model_name?: string
|
||||
name?: string
|
||||
description?: string
|
||||
status?: string
|
||||
models?: CompareModelRef[] | string
|
||||
load_status?: string | { loaded_models: LoadedModel[] }
|
||||
create_time?: string
|
||||
}
|
||||
|
||||
export interface CompareModelRef {
|
||||
model_id: number | string
|
||||
model_name: string
|
||||
model_path: string
|
||||
gpu_id: number
|
||||
source?: string
|
||||
port?: number
|
||||
}
|
||||
|
||||
// ============ 模型评测 ============
|
||||
export type EvalType = 'custom' | 'baseline'
|
||||
export type DimensionType = 'classification' | 'metric' | 'text_similarity'
|
||||
|
||||
export interface EvalTask {
|
||||
id: number | string
|
||||
eval_task_name?: string
|
||||
model_name?: string
|
||||
model_id?: number | string
|
||||
dataset?: string
|
||||
metric?: string
|
||||
score?: number
|
||||
status?: string
|
||||
create_time?: string
|
||||
}
|
||||
|
||||
export interface Dimension {
|
||||
id: number | string
|
||||
name: string
|
||||
type: DimensionType | string
|
||||
description?: string
|
||||
eval_model?: string
|
||||
eval_method?: string | string[]
|
||||
eval_prompt?: string
|
||||
is_active?: boolean
|
||||
is_default?: boolean
|
||||
bleu_n?: number
|
||||
output_precision?: number
|
||||
score_min?: number
|
||||
score_max?: number
|
||||
pass_threshold?: number
|
||||
create_time?: string
|
||||
}
|
||||
|
||||
// ============ 系统监控 ============
|
||||
export interface GpuInfo {
|
||||
name: string
|
||||
gpu_percent: number
|
||||
memory_used_gb: number
|
||||
memory_total_gb: number
|
||||
temperature: number
|
||||
power_w: number
|
||||
fan_speed?: number
|
||||
clock_mhz?: number
|
||||
driver_version?: string
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
cpu?: {
|
||||
percent: number
|
||||
cores: number
|
||||
percents?: number[]
|
||||
}
|
||||
memory?: {
|
||||
used_gb: number
|
||||
total_gb: number
|
||||
percent: number
|
||||
available_gb: number
|
||||
cached_gb: number
|
||||
}
|
||||
disk?: {
|
||||
used_gb: number
|
||||
total_gb: number
|
||||
percent: number
|
||||
}
|
||||
gpu?: GpuInfo[]
|
||||
network?: {
|
||||
download_mb?: number
|
||||
upload_mb?: number
|
||||
}
|
||||
system?: {
|
||||
uptime_seconds?: number
|
||||
process_count?: number
|
||||
os?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface HealthMetrics {
|
||||
cpu_percent?: number
|
||||
memory_percent?: number
|
||||
disk_percent?: number
|
||||
}
|
||||
|
||||
// ============ 日志 ============
|
||||
export interface LogFile {
|
||||
file: string
|
||||
name: string
|
||||
size: string
|
||||
pid?: number
|
||||
date?: string
|
||||
}
|
||||
|
||||
export interface LogContent {
|
||||
content: string
|
||||
file: string
|
||||
size: string
|
||||
}
|
||||
|
||||
export interface TrainingLogFile extends LogFile {
|
||||
pid?: number
|
||||
}
|
||||
|
||||
// ============ 其他工具 ============
|
||||
export interface CustomTool {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
url: string
|
||||
icon: string
|
||||
}
|
||||
Reference in New Issue
Block a user