├── .gitignore
├── LICENSE.txt
├── README.md
├── backend
├── .env
├── .gitignore
├── .gitkeep
├── README.md
├── app
│ ├── api
│ │ ├── common
│ │ │ ├── typing_utils.py
│ │ │ └── utils.py
│ │ ├── model
│ │ │ ├── base_model.py
│ │ │ ├── captioning_model.py
│ │ │ ├── captioning_model_mgr.py
│ │ │ ├── hunyuan_paramter.py
│ │ │ ├── training_paramter.py
│ │ │ └── wan_paramter.py
│ │ ├── proxy_tb.py
│ │ ├── resources
│ │ │ ├── current_task.py
│ │ │ ├── file.py
│ │ │ ├── gpu_log.py
│ │ │ ├── hunyuan_trainning.py
│ │ │ ├── tagging.py
│ │ │ ├── task_history.py
│ │ │ ├── training.py
│ │ │ ├── upload.py
│ │ │ ├── wan_dataset.py
│ │ │ └── wan_trainning.py
│ │ ├── run.py
│ │ ├── schema
│ │ │ ├── common_valid.py
│ │ │ └── upload_valid.py
│ │ └── swagger
│ │ │ └── swagger_config.py
│ └── service
│ │ ├── captioning.py
│ │ ├── florence2_captioning.py
│ │ ├── hunyuan_train.py
│ │ ├── joycaption2_llm_captioning.py
│ │ ├── task.py
│ │ ├── train.py
│ │ ├── upload.py
│ │ ├── wan_dataset.py
│ │ └── wan_train.py
├── pyproject.toml
├── requirements.txt
├── task
│ ├── manager.py
│ └── task.py
├── tests
│ ├── test_captioning.py
│ ├── test_hunyuan_paramter.py
│ ├── test_parameter_model.py
│ ├── test_task.py
│ └── test_utils.py
├── tools
│ ├── args2models.py
│ ├── flux_args_to_parameter.py
│ ├── wan.sh
│ └── wan_args_to_parameter.py
└── utils
│ └── util.py
├── docs
└── README.zh-CN.md
├── frontend
├── .editorconfig
├── .env.example
├── .env.production
├── .eslintignore
├── .eslintrc-auto-import.cjs
├── .prettierignore
├── .prettierrc.json
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── README.md
├── eslint.config.js
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── hey.svg
│ │ ├── loading_bg_dark.png
│ │ └── loading_bg_light.png
│ └── styles
│ │ └── loading.css
├── src
│ ├── App.vue
│ ├── api
│ │ ├── common
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── lora
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── monitor
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── tag
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── task
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── types.ts
│ ├── assets
│ │ ├── icons
│ │ │ └── remixicon.symbol.svg
│ │ └── images
│ │ │ ├── 404.svg
│ │ │ ├── about
│ │ │ ├── service_dark.png
│ │ │ ├── service_light.png
│ │ │ ├── wx_gzh_dark.png
│ │ │ └── wx_gzh_light.png
│ │ │ ├── ai-dataset
│ │ │ ├── image_icon.svg
│ │ │ └── text_icon.svg
│ │ │ ├── favicon
│ │ │ ├── 0.png
│ │ │ ├── 1.png
│ │ │ ├── 10.png
│ │ │ ├── 11.png
│ │ │ ├── 12.png
│ │ │ ├── 13.png
│ │ │ ├── 14.png
│ │ │ ├── 15.png
│ │ │ ├── 16.png
│ │ │ ├── 17.png
│ │ │ ├── 18.png
│ │ │ ├── 19.png
│ │ │ ├── 2.png
│ │ │ ├── 20.png
│ │ │ ├── 21.png
│ │ │ ├── 22.png
│ │ │ ├── 23.png
│ │ │ ├── 24.png
│ │ │ ├── 25.png
│ │ │ ├── 26.png
│ │ │ ├── 27.png
│ │ │ ├── 28.png
│ │ │ ├── 29.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ ├── 7.png
│ │ │ ├── 8.png
│ │ │ └── 9.png
│ │ │ ├── logo_dark.svg
│ │ │ ├── logo_light.svg
│ │ │ └── user-avatar.jpg
│ ├── components
│ │ ├── AiDataset
│ │ │ ├── ContextMenu.vue
│ │ │ ├── DatasetPagination.vue
│ │ │ ├── TagEdit.vue
│ │ │ ├── ai-dataset.helper.ts
│ │ │ ├── context-menu.helper.ts
│ │ │ ├── index.vue
│ │ │ └── types.ts
│ │ ├── Card
│ │ │ └── Card.vue
│ │ ├── Collapse
│ │ │ ├── Collapse.vue
│ │ │ └── SimpleCollapse.vue
│ │ ├── Dialog
│ │ │ ├── LoRASavePathWarningDialog.vue
│ │ │ ├── LoraTaskLogDialog.vue
│ │ │ └── NetworkRetryDialog.vue
│ │ ├── Drawer
│ │ │ └── ViewSamplingDrawer.vue
│ │ ├── FieldSetWrapper
│ │ │ └── FieldSetWrapper.vue
│ │ ├── FileManager
│ │ │ ├── FileItem
│ │ │ │ ├── ImageFile.vue
│ │ │ │ ├── TextFile.vue
│ │ │ │ └── VideoFile.vue
│ │ │ ├── index.ts
│ │ │ ├── style.scss
│ │ │ └── types.ts
│ │ ├── Form
│ │ │ ├── BaseSelector.vue
│ │ │ ├── DataSet-v2
│ │ │ │ ├── TagAddGlobalPromptSwitch.vue
│ │ │ │ ├── TagAdvancedSwitch.vue
│ │ │ │ ├── TagAppendSwitch.vue
│ │ │ │ ├── TagDirectory.vue
│ │ │ │ ├── TagGlobalPrompt.vue
│ │ │ │ ├── TagJoyCaptionPrompt.vue
│ │ │ │ ├── TagJoyCaptionPromptTypeSelect.vue
│ │ │ │ ├── TagModelSelect.vue
│ │ │ │ ├── TagResetButton.vue
│ │ │ │ └── TagSubmitButton.vue
│ │ │ ├── FileManager.vue
│ │ │ ├── FileSelector.vue
│ │ │ ├── FluxNetworkModuleSelect.vue
│ │ │ ├── FolderSelector.vue
│ │ │ ├── InputTreeSelector.vue
│ │ │ ├── LrSchedulerSelect.vue
│ │ │ ├── ModelSaveFormatSelector.vue
│ │ │ ├── ModelSavePrecisionSelector.vue
│ │ │ ├── OptimizerTypeSelect.vue
│ │ │ ├── PopoverFormItem.vue
│ │ │ ├── SDXLNetworkModuleSelect.vue
│ │ │ ├── WeightingSchemeSelect.vue
│ │ │ └── ZLSwitch.vue
│ │ ├── GlobalModalManager
│ │ │ └── index.vue
│ │ ├── Icon
│ │ │ └── Icon.vue
│ │ ├── Monitor
│ │ │ ├── GPUMonitor
│ │ │ │ └── index.vue
│ │ │ ├── HYTrainingMonitor
│ │ │ │ └── index.vue
│ │ │ ├── LoRATrainingMonitor
│ │ │ │ └── index.vue
│ │ │ ├── TagMonitor
│ │ │ │ └── index.vue
│ │ │ └── WanTrainingMonitor
│ │ │ │ └── index.vue
│ │ ├── Split
│ │ │ ├── SplitRightPanel
│ │ │ │ └── index.vue
│ │ │ └── TwoSplit
│ │ │ │ └── index.vue
│ │ ├── TaskLog
│ │ │ └── index.vue
│ │ ├── TeleportFooterBarContent
│ │ │ └── index.vue
│ │ ├── Toml
│ │ │ └── TomlPreview.vue
│ │ └── VideoPreview
│ │ │ └── index.vue
│ ├── directives
│ │ ├── complexity
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── types.ts
│ ├── enums
│ │ ├── complexity.enum.ts
│ │ └── split-right.enum.ts
│ ├── hooks
│ │ ├── task
│ │ │ ├── types.ts
│ │ │ ├── useFluxLora
│ │ │ │ └── index.ts
│ │ │ ├── useGPU
│ │ │ │ └── index.ts
│ │ │ ├── useHYLora
│ │ │ │ └── index.ts
│ │ │ ├── useTag
│ │ │ │ └── index.ts
│ │ │ └── useWanLora
│ │ │ │ └── index.ts
│ │ ├── useAuth.ts
│ │ ├── useEnhancedStorage.ts
│ │ ├── useIcon.ts
│ │ ├── useImageViewer.ts
│ │ ├── useRedirect.ts
│ │ └── useVideoPreview.ts
│ ├── init-lora-trainer
│ │ ├── animated-favicon.ts
│ │ ├── index.ts
│ │ └── task.ts
│ ├── layout
│ │ ├── admin-layout
│ │ │ ├── components
│ │ │ │ ├── Aside
│ │ │ │ │ ├── Footer
│ │ │ │ │ │ ├── LightDarkToggle.vue
│ │ │ │ │ │ ├── MenuCollapse.vue
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ ├── Logo.vue
│ │ │ │ │ ├── Menu.vue
│ │ │ │ │ ├── MenuItem.vue
│ │ │ │ │ └── index.vue
│ │ │ │ ├── FooterBar
│ │ │ │ │ └── index.vue
│ │ │ │ └── Main
│ │ │ │ │ └── index.vue
│ │ │ └── index.vue
│ │ └── blank-layout
│ │ │ └── index.vue
│ ├── main.ts
│ ├── plugins
│ │ └── element-plus.ts
│ ├── request
│ │ ├── axios.d.ts
│ │ ├── core.ts
│ │ ├── helper.ts
│ │ ├── index.ts
│ │ └── types.ts
│ ├── router
│ │ ├── guard.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── route-modules
│ │ │ ├── about.ts
│ │ │ ├── ai-dataset.ts
│ │ │ ├── iframe.ts
│ │ │ ├── index.ts
│ │ │ ├── lora.ts
│ │ │ ├── settings.ts
│ │ │ ├── task.ts
│ │ │ ├── website.ts
│ │ │ └── zl.ts
│ │ ├── router-auth
│ │ │ ├── context.ts
│ │ │ ├── index.ts
│ │ │ ├── strategy
│ │ │ │ ├── guest-strategy.ts
│ │ │ │ ├── public-strategy.ts
│ │ │ │ └── required-strategy.ts
│ │ │ └── types.ts
│ │ └── routes.ts
│ ├── stores
│ │ ├── index.ts
│ │ └── modules
│ │ │ ├── app
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ │ ├── index.ts
│ │ │ ├── modal-manager
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ │ ├── settings
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ │ ├── training
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ │ └── user
│ │ │ └── index.ts
│ ├── styles
│ │ ├── element-plus
│ │ │ ├── element-plus-reset.scss
│ │ │ ├── theme-dark.scss
│ │ │ └── theme-light.scss
│ │ ├── index.scss
│ │ ├── mixins.scss
│ │ ├── nprogress.scss
│ │ ├── reset.scss
│ │ ├── theme
│ │ │ ├── dark.scss
│ │ │ └── light.scss
│ │ ├── transition.scss
│ │ └── variables.scss
│ ├── utils
│ │ ├── dayjs.ts
│ │ ├── env.ts
│ │ ├── event-bus
│ │ │ ├── events.ts
│ │ │ └── index.ts
│ │ ├── file-manager
│ │ │ ├── enums.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── log.ts
│ │ ├── lora.helper
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── monitor
│ │ │ ├── core
│ │ │ │ ├── base-monitor.ts
│ │ │ │ └── lora-monitor.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── nprogress.ts
│ │ ├── toml
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tools.ts
│ └── views
│ │ ├── about
│ │ └── index.vue
│ │ ├── ai-dataset
│ │ └── index.vue
│ │ ├── error
│ │ └── 404.vue
│ │ ├── iframe
│ │ └── index.vue
│ │ ├── lora
│ │ ├── flux
│ │ │ ├── components
│ │ │ │ ├── AdvancedSettings
│ │ │ │ │ ├── AdvancedOptions.vue
│ │ │ │ │ ├── CaptionOptions.vue
│ │ │ │ │ ├── DataEnhancerOptions.vue
│ │ │ │ │ ├── DistributedTrainingOptions.vue
│ │ │ │ │ ├── FluxOptions.vue
│ │ │ │ │ ├── LRAndOptimizer.vue
│ │ │ │ │ ├── LoggingOptions.vue
│ │ │ │ │ ├── NetworkOptions.vue
│ │ │ │ │ ├── SpeedOptimizationOptions.vue
│ │ │ │ │ ├── TrainingOptions.vue
│ │ │ │ │ ├── TrainingPreviewOptions.vue
│ │ │ │ │ └── index.vue
│ │ │ │ ├── BasicInfo
│ │ │ │ │ └── index.vue
│ │ │ │ ├── FluxDataset
│ │ │ │ │ └── index.vue
│ │ │ │ ├── ModelParameters
│ │ │ │ │ └── index.vue
│ │ │ │ ├── TrainingData
│ │ │ │ │ └── index.vue
│ │ │ │ └── TrainingSamples
│ │ │ │ │ └── index.vue
│ │ │ ├── flux.helper.ts
│ │ │ ├── flux.validate.ts
│ │ │ ├── index.vue
│ │ │ └── types.ts
│ │ ├── hunyuan-video
│ │ │ ├── components
│ │ │ │ ├── AdvancedSettings
│ │ │ │ │ ├── AdapterSettings.vue
│ │ │ │ │ ├── EvalSettings.vue
│ │ │ │ │ ├── MiscSettings.vue
│ │ │ │ │ ├── OptimizerSettings.vue
│ │ │ │ │ └── index.vue
│ │ │ │ ├── BasicInfo
│ │ │ │ │ └── index.vue
│ │ │ │ ├── Form
│ │ │ │ │ ├── AdapterTypeSelector.vue
│ │ │ │ │ ├── ModelDtypeSelector.vue
│ │ │ │ │ ├── ModelTimestepSampleMethodSelector.vue
│ │ │ │ │ ├── ModelTransformerDtypeSelector.vue
│ │ │ │ │ ├── OptimizerTypeSelector.vue
│ │ │ │ │ ├── PartitionMethodSelector.vue
│ │ │ │ │ └── VideoClipModeSelector.vue
│ │ │ │ ├── HYDataset
│ │ │ │ │ └── index.vue
│ │ │ │ ├── ModelParameters
│ │ │ │ │ └── index.vue
│ │ │ │ └── TrainingData
│ │ │ │ │ └── index.vue
│ │ │ ├── config.toml
│ │ │ ├── dataset.toml
│ │ │ ├── hunyuan.helper.ts
│ │ │ ├── index.vue
│ │ │ └── types.ts
│ │ ├── sdxl
│ │ │ ├── components
│ │ │ │ ├── AdvancedSettings
│ │ │ │ │ ├── AdvancedOptions.vue
│ │ │ │ │ ├── CaptionOptions.vue
│ │ │ │ │ ├── DataEnhancerOptions.vue
│ │ │ │ │ ├── DistributedTrainingOptions.vue
│ │ │ │ │ ├── LRAndOptimizer.vue
│ │ │ │ │ ├── LoggingOptions.vue
│ │ │ │ │ ├── NetworkOptions.vue
│ │ │ │ │ ├── NoiseOptions.vue
│ │ │ │ │ ├── SpeedOptimizationOptions.vue
│ │ │ │ │ ├── TrainingOptions.vue
│ │ │ │ │ ├── TrainingPreviewOptions.vue
│ │ │ │ │ └── index.vue
│ │ │ │ ├── BasicInfo
│ │ │ │ │ └── index.vue
│ │ │ │ ├── ModelParameters
│ │ │ │ │ └── index.vue
│ │ │ │ └── TrainingData
│ │ │ │ │ └── index.vue
│ │ │ ├── index.vue
│ │ │ └── types.ts
│ │ └── wan-video
│ │ │ ├── components
│ │ │ ├── AdvancedSettings
│ │ │ │ ├── AdvancedMemoryOptimizer.vue
│ │ │ │ ├── DiffusionModelOptions.vue
│ │ │ │ ├── ModelOptions.vue
│ │ │ │ ├── OptimizerAndLROptions.vue
│ │ │ │ ├── TrainingControlOptions.vue
│ │ │ │ ├── TrainingDistributedOptions.vue
│ │ │ │ └── index.vue
│ │ │ ├── BasicInfo.vue
│ │ │ ├── SampleValidator.vue
│ │ │ ├── TrainingData.vue
│ │ │ └── WanDataSet
│ │ │ │ ├── ImageDataSet.vue
│ │ │ │ ├── VideoDataSet.vue
│ │ │ │ └── index.vue
│ │ │ ├── index.vue
│ │ │ ├── types.ts
│ │ │ ├── wan.helper.ts
│ │ │ └── wan.validate.ts
│ │ ├── settings
│ │ └── index.vue
│ │ ├── task
│ │ ├── components
│ │ │ ├── HYDetail.vue
│ │ │ ├── LoraTaskDetail.vue
│ │ │ ├── TagTaskDetail.vue
│ │ │ ├── TaskEmpty.vue
│ │ │ ├── TaskItem.vue
│ │ │ └── WanDetail.vue
│ │ ├── index.vue
│ │ └── task.helper.ts
│ │ └── website
│ │ ├── components
│ │ └── Banner.vue
│ │ └── index.vue
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── types
│ ├── admin-app.d.ts
│ ├── auto-imports.d.ts
│ ├── components.d.ts
│ ├── directives.d.ts
│ ├── element-plus.d.ts
│ ├── env.d.ts
│ ├── router.d.ts
│ ├── task.d.ts
│ └── type-utils.d.ts
├── vite-plugins
│ └── git-commit-info.ts
└── vite.config.ts
├── public
└── images
│ └── logo.png
└── pytest.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
30 | *.tsbuildinfo
31 |
32 | __pycache__/
33 | upload/
34 | .vscode/
35 | .coverage*
36 |
37 | # frontend
38 | /frontend/.env.development
39 |
--------------------------------------------------------------------------------
/backend/.env:
--------------------------------------------------------------------------------
1 | FLASK_ENV=development # 当前环境
2 | #FLASK_DEBUG=True # 开启 debug mode
3 | FLASK_APP=run.py # flask项目入口文件
4 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | models/
2 | captions_output
3 | configs
4 | .coverage
5 | outputs
6 | sd-scripts/
7 | .vscode/
8 | venv/
9 | nohup.out
10 | diffusion-pipe/
11 | musubi-tuner/
12 | .venv/
13 |
--------------------------------------------------------------------------------
/backend/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/backend/.gitkeep
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # 智灵训练器 后端
2 |
3 | ## 运行
4 |
5 | ```bash
6 |
7 | python3 -m venv .venv # 创建虚拟环境
8 |
9 | . .venv/bin/activate # 激活虚拟环境
10 |
11 | pip install -r requirements.txt # 安装依赖
12 |
13 | python -m app.api.run # 运行
14 | ```
15 |
16 | ## tensorboard 运行
17 | ```bash
18 | tensorboard --logdir=logs
19 |
20 | ```
21 |
22 | ## swagger文档
23 | http://127.0.0.1:5000/apidocs/#/
--------------------------------------------------------------------------------
/backend/app/api/common/typing_utils.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | def is_generic(klass):
4 | """ Determine whether klass is a generic class """
5 | return hasattr(klass, '__origin__')
6 |
7 | def is_dict(klass):
8 | """ Determine whether klass is a Dict """
9 | return klass.__origin__ == dict
10 |
11 | def is_list(klass):
12 | """ Determine whether klass is a List """
13 | return klass.__origin__ == list
14 |
--------------------------------------------------------------------------------
/backend/app/api/model/base_model.py:
--------------------------------------------------------------------------------
1 | import typing
2 | from dataclasses import dataclass
3 |
4 | T = typing.TypeVar('T')
5 |
6 |
7 | @dataclass
8 | class Model:
9 |
10 | def _from_dict(self, d=None):
11 | if d is not None:
12 | for key, value in d.items():
13 | setattr(self, key, value)
14 | return self
15 |
16 |
--------------------------------------------------------------------------------
/backend/app/api/model/captioning_model.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import List, Optional, Dict, Callable
3 | from dataclasses import dataclass
4 | from ..common.utils import getmodelpath
5 | import dacite
6 | from utils.util import setup_logging
7 | setup_logging()
8 | import logging
9 | logger = logging.getLogger(__name__)
10 |
11 | @dataclass
12 | class CaptioningModelInfo:
13 | path : str
14 | name : str
15 | cache_dir : str #models/joy-caption-alpha-two/cgrkzexw-599808
16 | captioning: Optional[Callable]
17 | llm_cache_dir : str = "" #models/llm/Meta-Llama-3.1-8B-Instruct-bnb-4bit
18 | clip_cache_dir: str = "" #models/siglip-so400m-patch14-384
19 |
20 | @staticmethod
21 | def from_dict(key: str, dikt: dict) -> 'CaptioningModelInfo':
22 | obj = dacite.from_dict(data_class=CaptioningModelInfo, data=dikt)
23 |
24 | if not os.path.isabs(obj.cache_dir):
25 | obj.cache_dir = os.path.join(getmodelpath(), obj.cache_dir)
26 |
27 | if not is_empty_str(obj.llm_cache_dir) and not os.path.isabs(obj.llm_cache_dir):
28 | obj.llm_cache_dir = os.path.join(getmodelpath(), obj.llm_cache_dir)
29 |
30 | if not is_empty_str(obj.clip_cache_dir) and not os.path.isabs(obj.clip_cache_dir):
31 | obj.clip_cache_dir = os.path.join(getmodelpath(), obj.clip_cache_dir)
32 |
33 | obj.cache_dir = os.path.join(getmodelpath(), key)
34 | if obj.path != ".":
35 | obj.path = os.path.join(os.path.join(getmodelpath(), key), obj.path)
36 |
37 | logger.info(f"model_info is {obj}")
38 | return obj
39 |
40 | def is_empty_str(s: str) -> bool:
41 | return not s or s.isspace()
42 |
--------------------------------------------------------------------------------
/backend/app/api/model/captioning_model_mgr.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Dict, List
2 | from dataclasses import dataclass
3 | from .captioning_model import CaptioningModelInfo
4 | from app.service.florence2_captioning import florence2_captioning
5 | from app.service.joycaption2_llm_captioning import joycaption2_llm_captioning
6 | import os
7 |
8 |
9 | @dataclass
10 | class CaptioningModelManager:
11 | models : Optional[Dict[str, CaptioningModelInfo]]
12 |
13 | @staticmethod
14 | def get_model_mgr() -> 'CaptioningModelManager':
15 | return CaptioningModelManager(models={
16 | "florence2": CaptioningModelInfo.from_dict("florence2", {
17 | "path": "models--multimodalart--Florence-2-large-no-flash-attn",
18 | "name": "multimodalart/Florence-2-large-no-flash-attn",
19 | "cache_dir": "",
20 | "captioning": florence2_captioning
21 |
22 | }),
23 | "joy-caption-alpha-two": CaptioningModelInfo.from_dict("joy-caption-alpha-two", {
24 | "path": ".",
25 | "name": "joy-caption-alpha-two",
26 | "cache_dir": "joy-caption-alpha-two",
27 | "llm_cache_dir": os.path.join("llm", "Meta-Llama-3.1-8B-Instruct-bnb-4bit"),
28 | "clip_cache_dir": os.path.join("clip", "siglip-so400m-patch14-384"),
29 | "captioning": joycaption2_llm_captioning
30 | })
31 | })
32 |
33 | def get_model(self, model_name: str) -> CaptioningModelInfo:
34 | model = self.models.get(model_name, None)
35 | if model is None:
36 | raise ValueError(f"only support model name: {self.models.keys()}, but request model name is {model_name}")
37 | return model
38 |
39 | def keys(self) -> List[str]:
40 | return self.models.keys()
41 |
42 |
43 |
44 |
45 | cap_model_mgr = CaptioningModelManager.get_model_mgr()
--------------------------------------------------------------------------------
/backend/app/api/resources/current_task.py:
--------------------------------------------------------------------------------
1 |
2 | from flask import request
3 | from flask_restful import Resource
4 | from app.service.task import TaskService
5 | from ..common.utils import use_swagger_config
6 | from ..swagger.swagger_config import task_current
7 |
8 | from utils.util import setup_logging
9 | setup_logging()
10 | import logging
11 | logger = logging.getLogger(__name__)
12 |
13 | class CurrentTask(Resource):
14 |
15 | @use_swagger_config(task_current)
16 | def get(self):
17 | try :
18 | return TaskService().current(), 200
19 | except FileNotFoundError as e:
20 | return {
21 | 'success': False,
22 | 'msg': str(e)
23 | }, 404
24 | except Exception as e:
25 | logger.error(f"get current task failed: {e}", exc_info=True)
26 | return {
27 | 'success': False,
28 | 'msg': f"Internal server error: {e}"
29 | }, 500
30 |
--------------------------------------------------------------------------------
/backend/app/api/resources/gpu_log.py:
--------------------------------------------------------------------------------
1 | from flask_restful import Resource
2 | from ..common.utils import res, use_swagger_config
3 | from ..swagger.swagger_config import gpu_log_config
4 | from app.service.train import TrainingService
5 |
6 | class GpuLog(Resource):
7 |
8 | @use_swagger_config(gpu_log_config)
9 | def get(self):
10 | """
11 | 获取 GPU 数据信息。
12 | """
13 | try:
14 | # 获取 GPU 数据
15 | gpu_info = TrainingService().get_gpu_info()
16 |
17 | if "error" in gpu_info:
18 | return res(success=False,message=gpu_info["error"])
19 |
20 | return res(data=gpu_info)
21 |
22 | except Exception as e:
23 | return res(success=False,message=str(e))
24 |
--------------------------------------------------------------------------------
/backend/app/api/resources/hunyuan_trainning.py:
--------------------------------------------------------------------------------
1 | from flask import request
2 | from flask_restful import Resource
3 | from app.service.hunyuan_train import HunyuanTrainingService
4 | from task.task import Task
5 | from ..common.utils import use_swagger_config, res
6 | from ..swagger.swagger_config import huanyuan_training
7 | from ..model.hunyuan_paramter import HunyuanTrainingParameter
8 |
9 |
10 | from utils.util import setup_logging
11 | setup_logging()
12 | import logging
13 | logger = logging.getLogger(__name__)
14 |
15 | class HunyuanTraining(Resource):
16 | @use_swagger_config(huanyuan_training)
17 | def post(self):
18 | """
19 | 启动混元训练任务
20 | """
21 | # 解析请求中的 JSON 数据
22 | data = request.get_json()
23 | parameter = None
24 | try:
25 | parameter = HunyuanTrainingParameter.from_dict(data)
26 |
27 | valid, reson = HunyuanTrainingParameter.validate(parameter)
28 | if not valid:
29 | return res(success=False, message=reson, code=400), 400
30 |
31 | task = HunyuanTrainingService().start_train(parameter)
32 | return res(
33 | data={"task_id": task.id},
34 | message=f"training task {task.id} started successfully."
35 | )
36 |
37 | except ValueError as e:
38 | logger.warning(f"start training with parameter:{parameter} failed, error:", exc_info=e)
39 | return res(success=False,
40 | message=f"Your training parameters were incorrect, please fix them. detail info:{str(e)}", code=400), 400
41 | except Exception as e:
42 | logger.warning(f"start training with parameter:{parameter} failed, error:", exc_info=e)
43 | return res(success=False, message="Server Interal Error, please contact the administrator", code=500), 500
--------------------------------------------------------------------------------
/backend/app/api/resources/task_history.py:
--------------------------------------------------------------------------------
1 |
2 | from flask import request
3 | from flask_restful import Resource
4 | from app.service.task import TaskService
5 | from ..common.utils import use_swagger_config
6 | from ..swagger.swagger_config import task_history, task_run_log
7 |
8 | from utils.util import setup_logging
9 | setup_logging()
10 | import logging
11 | logger = logging.getLogger(__name__)
12 |
13 | class TaskHistory(Resource):
14 |
15 | @use_swagger_config(task_history)
16 | def get(self):
17 | try :
18 | task_id = request.args.get('task_id')
19 | show_config = True if request.args.get('show_config', "false").upper() == "TRUE" else False
20 | return TaskService().get(task_id, show_config), 200
21 | except FileNotFoundError as e:
22 | return {
23 | 'success': False,
24 | 'msg': str(e)
25 | }, 404
26 | except Exception as e:
27 | logger.error(f"get current task failed: {e}", exc_info=True)
28 | return {
29 | 'success': False,
30 | 'msg': f"Internal server error: {e}"
31 | }, 500
32 |
33 |
34 |
35 |
36 | class TaskRunLog(Resource):
37 | @use_swagger_config(task_run_log)
38 | def get(self):
39 | try:
40 | task_id = request.args.get('task_id')
41 | return TaskService().get_log(task_id=task_id)
42 | except FileNotFoundError as e:
43 | return {
44 | 'success': False,
45 | 'msg': str(e)
46 | }, 404
47 | except Exception as e:
48 | logger.error(f"get current task failed: {e}", exc_info=True)
49 | return {
50 | 'success': False,
51 | 'msg': f"Internal server error: {e}"
52 | }, 500
--------------------------------------------------------------------------------
/backend/app/api/resources/wan_dataset.py:
--------------------------------------------------------------------------------
1 | import dacite
2 | from typing import Tuple
3 | from flask import request
4 | from flask_restful import Resource
5 | from app.service.wan_dataset import WanDatasetService
6 | from app.api.common.utils import use_swagger_config, res
7 | from app.api.model.wan_paramter import WanDataSetConfig, FrameExtractionMethod
8 | from app.api.swagger.swagger_config import wan_dataset_estimate
9 |
10 | from utils.util import setup_logging
11 | setup_logging()
12 | import logging
13 | logger = logging.getLogger(__name__)
14 |
15 | class WanDatasets(Resource):
16 |
17 | @use_swagger_config(wan_dataset_estimate)
18 | def post(self):
19 | data = request.get_json()
20 | dataset = dacite.from_dict(data_class=WanDataSetConfig, data=data,
21 | config=dacite.Config(
22 | type_hooks={
23 | Tuple[int, int]: lambda x: tuple(x),
24 | FrameExtractionMethod: lambda x: FrameExtractionMethod(x) if x else None
25 | }
26 | )
27 | )
28 | try:
29 | total_images, total_batches = WanDatasetService().eastimate_video_dataset_count(dataset)
30 | return res(data={"total_images": total_images, "total_batches": total_batches},)
31 | except Exception as e:
32 | logger.error(f"eastimate frame in dataset {data} failed ", exc_info=e)
33 | return res(message=f"Server interal error: {str(e)}", code=500)
--------------------------------------------------------------------------------
/backend/app/api/resources/wan_trainning.py:
--------------------------------------------------------------------------------
1 | from flask import request
2 | from flask_restful import Resource
3 | from app.service.wan_train import WanTrainingService
4 | from app.api.common.utils import use_swagger_config, res
5 | from app.api.swagger.swagger_config import wan_training_api_config
6 | from app.api.model.wan_paramter import WanTrainingParameter
7 |
8 |
9 | from utils.util import setup_logging
10 | setup_logging()
11 | import logging
12 | logger = logging.getLogger(__name__)
13 |
14 | class WanTraining(Resource):
15 | @use_swagger_config(wan_training_api_config)
16 | def post(self):
17 | """
18 | 启动Wan(万象)的训练任务
19 | """
20 | # 解析请求中的 JSON 数据
21 | data = request.get_json()
22 | parameter = None
23 | try:
24 | parameter = WanTrainingParameter.from_dict(data)
25 | parameter = WanTrainingParameter.validate(parameter)
26 |
27 | task = WanTrainingService().start_train(parameter)
28 | return res(
29 | data={"task_id": task.id},
30 | message=f"training task {task.id} started successfully."
31 | )
32 |
33 | except ValueError as e:
34 | logger.warning(f"start training with parameter:{parameter} failed, error:", exc_info=e)
35 | return res(success=False,
36 | message=f"Your training parameters were incorrect, please fix them. detail info:{str(e)}", code=400), 400
37 | except Exception as e:
38 | logger.warning(f"start training with parameter:{parameter} failed, error:", exc_info=e)
39 | return res(success=False, message="Server Interal Error, please contact the administrator", code=500), 500
--------------------------------------------------------------------------------
/backend/app/api/schema/common_valid.py:
--------------------------------------------------------------------------------
1 | # 打标校验
2 | def tagging_args_valid(parser):
3 | parser.add_argument(
4 | "model_name",
5 | type=str,
6 | required=True,
7 | help="请选择模型",
8 | )
9 | parser.add_argument(
10 | "image_path",
11 | type=str,
12 | required=True,
13 | help="请输入图片路径",
14 | )
15 |
16 | # 手动打标校验
17 | def manual_tagging_args_valid(parser):
18 | parser.add_argument(
19 | "image_path",
20 | type=str,
21 | required=True,
22 | help="请选择图片",
23 | )
24 | parser.add_argument(
25 | "caption_text",
26 | type=str,
27 | required=True,
28 | help="请填写打标文案",
29 | )
30 |
31 | # 文件上传校验
32 | def file_args_valid(parser):
33 | parser.add_argument(
34 | "path", type=str, required=True, default="/", help="path is required"
35 | )
36 | parser.add_argument("level", type=int, default=0, help="Directory level")
37 | parser.add_argument(
38 | "parent_id", type=str, required=True, default="", help="parent_id is required"
39 | )
40 |
41 | # 保存配置
42 | def save_config_args_valid(parser):
43 | parser.add_argument("config_name", type=str, required=True, help="配置名称不能为空")
44 | parser.add_argument("config_content", type=str, required=True, help="配置内容不能为空")
45 |
46 | # 读取配置
47 | def get_config_args_valid(parser):
48 | parser.add_argument("config_name", type=str, required=True,location="args", help="配置名称不能为空")
49 |
50 | # 启动训练
51 | def start_args_valid(parser):
52 | parser.add_argument(
53 | "config_name",
54 | type=str,
55 | required=True,
56 | location="json", # 从请求体中解析配置名称
57 | help="配置名称不能为空",
58 | )
--------------------------------------------------------------------------------
/backend/app/api/schema/upload_valid.py:
--------------------------------------------------------------------------------
1 | from flask import request
2 |
3 |
4 | def validate_upload_args():
5 | """校验上传接口的参数"""
6 | # 校验文件字段
7 | files = request.files.getlist("files")
8 | upload_path = request.args.get("upload_path")
9 | upload_id = request.args.get("upload_id")
10 | valid_files = [
11 | file for file in files if file.filename.strip()
12 | ] # 过滤掉空文件名的文件
13 |
14 | if not valid_files:
15 | return {"code": 400, "msg": "请上传文件"}, False
16 |
17 | if not upload_path:
18 | return {"code": 400, "msg": "缺少必需参数 upload_path"}, False
19 | if not upload_id:
20 | return {"code": 400, "msg": "缺少必需参数 upload_id"}, False
21 |
22 | return {
23 | "upload_path": upload_path,
24 | "upload_id": upload_id,
25 | "files": request.files.getlist("files"), # 获取多个文件
26 | }, True
27 |
--------------------------------------------------------------------------------
/backend/app/service/upload.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | from typing import List, Union
4 | from ..api.common.utils import res
5 |
6 | class UploadService:
7 | # 定义允许的文件扩展名
8 | ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp", "txt", "mp4", "mov", "avi", "mkv", "flv", "wmv"}
9 |
10 | # 生成唯一的文件名,避免文件名重复
11 | def generate_unique_filename(self, filename, upload_path):
12 | """
13 | 生成唯一的文件名,检查文件是否已存在,如果已存在,则加上数字后缀
14 | """
15 | filename_without_ext, ext = os.path.splitext(filename)
16 | counter = 1
17 | unique_filename = filename
18 |
19 | # 如果是txt文件,直接返回
20 | if ext == "txt":
21 | return unique_filename
22 |
23 | # 检查文件是否已存在,若已存在则加后缀
24 | while os.path.exists(os.path.join(upload_path, unique_filename)):
25 | unique_filename = f"{filename_without_ext}_{counter}{ext}"
26 | counter += 1
27 |
28 | return unique_filename
29 |
30 | # 检查文件是否是允许的图片类型
31 | def allowed_file(self, filename):
32 | return "." in filename and filename.rsplit(".", 1)[1].lower() in self.ALLOWED_EXTENSIONS
33 |
34 |
--------------------------------------------------------------------------------
/backend/app/service/wan_train.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | import uuid
4 | from task.task import Task
5 | from app.api.model.wan_paramter import WanTrainingParameter
6 | from app.api.common.utils import dataset2toml
7 | from datetime import datetime
8 | from typing import Optional
9 | import sys
10 |
11 | from utils.util import getprojectpath, setup_logging
12 |
13 | from task.manager import task_decorator
14 | setup_logging()
15 | import logging
16 | logger = logging.getLogger(__name__)
17 |
18 | class WanTrainingService():
19 |
20 | def __init__(self) -> 'WanTrainingService':
21 | self.module_path = os.path.join(getprojectpath(), "musubi-tuner")
22 | if not os.path.exists(self.module_path):
23 | raise FileNotFoundError(f"Training script not found at {self.module_path}")
24 |
25 | def start_train(self, parameter: WanTrainingParameter) -> Task:
26 | taskid = uuid.uuid4().hex
27 | timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
28 | parameter.config.logging_dir = os.path.join(getprojectpath(), "logs", f"wan-{taskid}-{timestamp}")
29 | os.makedirs(parameter.config.logging_dir, exist_ok=True)
30 | return self.run_train(parameter, task_id=taskid, module_path=self.module_path)
31 |
32 |
33 | @task_decorator
34 | def run_train(self, training_paramters: WanTrainingParameter, task_id: str=None, module_path:str = None):
35 | return Task.wrap_wan_training(training_paramters, task_id, module_path,
36 | is_sampling = True \
37 | if training_paramters.config.sample_prompts \
38 | and os.path.exists(training_paramters.config.sample_prompts) \
39 | else False)
--------------------------------------------------------------------------------
/backend/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pytest]
2 | pythonpath = "."
3 |
4 | [tool.pylint]
5 | init-hook='import sys; sys.path.append(".")'
6 |
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | dacite
2 | tensorboard
3 | pillow
4 | timm
5 | pyyaml
6 | oyaml
7 | flask >= 3.1.0
8 | pytest
9 | flask-restful == 0.3.10
10 | python-dotenv == 1.0.1
11 | flasgger == 0.9.7.1
12 | werkzeug == 3.1.3
13 | flask-cors == 5.0.0
14 | tbparse == 0.0.9
15 | python-dateutil == 2.8.2
16 | pytest>=7.0.0
17 | pytest-cov>=3.0.0
18 | pytest-watch>=4.2.0
19 | coverage>=6.0.0
20 | python-slugify
21 | toml
22 | huggingface_hub==0.25.0
23 | peft==0.14.0
24 | av==14.0.1
25 | #bitsandbytes==0.44.0
26 |
27 |
28 |
--------------------------------------------------------------------------------
/backend/tests/test_captioning.py:
--------------------------------------------------------------------------------
1 | from app.api.model.captioning_model_mgr import cap_model_mgr
2 | from utils.util import getmodelpath
3 | import os
4 |
5 | def test_captioning_model_mgr():
6 | model_info = cap_model_mgr.get_model("florence2")
7 |
8 | assert model_info is not None
9 | assert model_info.name == "multimodalart/Florence-2-large-no-flash-attn"
10 | path = os.path.join(getmodelpath(), "florence2", "models--multimodalart--Florence-2-large-no-flash-attn")
11 | assert model_info.path == path
12 | cache_dir = os.path.join(getmodelpath(), "florence2")
13 | assert model_info.cache_dir == cache_dir
14 |
--------------------------------------------------------------------------------
/backend/tests/test_task.py:
--------------------------------------------------------------------------------
1 | from task.task import TrainingTask, logger
2 |
3 | def test_task_parse_progress_line():
4 | task = TrainingTask()
5 | task.detail = {}
6 | task.parse_progress_line("steps: 100%|██████████| 40/40 [00:42<00:00, 1.07s/it, avr_loss=0.145]")
7 | logger.info(task.detail)
8 | assert task.detail['total'] == 40
9 | assert task.detail['elapsed'] == '00:42'
10 | assert task.detail['remaining'] == '00:00'
11 | assert task.detail['speed'] == 1.07
12 |
13 | def test_task_parse_stdout():
14 | task = TrainingTask()
15 | task.detail = {}
16 |
17 | # Test Japanese format
18 | for line in [
19 | "running training / 学習開始",
20 | "num train images * repeats / 学習画像の数×繰り返し回数: 10",
21 | "num reg images / 正則化画像の数: 0",
22 | "num batches per epoch / 1epochのバッチ数: 10",
23 | "num epochs / epoch数: 10",
24 | "batch size per device / バッチサイズ: 1",
25 | "gradient accumulation steps / 勾配を合計するステップ数 = 1",
26 | "total optimization steps / 学習ステップ数: 100"
27 | ]:
28 | task.parse_stdout(line)
29 |
30 | logger.info(task.detail)
31 | assert task.detail['num_train_images'] == 10
32 | assert task.detail['num_reg_images'] == 0
33 | assert task.detail['num_batches_per_epoch'] == 10
34 | assert task.detail['num_epochs'] == 10
35 | assert task.detail['batch_size_per_device'] == 1
36 | assert task.detail['gradient_accumulation_steps'] == 1
37 | assert task.detail['total_optimization_steps'] == 100
--------------------------------------------------------------------------------
/backend/tests/test_utils.py:
--------------------------------------------------------------------------------
1 |
2 | from utils.util import getmodelpath
3 | import pathlib
4 |
5 |
6 | def test_model_path():
7 | assert getmodelpath() == str(pathlib.Path("backend/models").absolute())
--------------------------------------------------------------------------------
/backend/tools/flux_args_to_parameter.py:
--------------------------------------------------------------------------------
1 | from args2models import init_advanced, loop_advanced_component
2 | from library import flux_train_utils
3 |
4 |
5 |
6 | def to_classe(advanced_component: List) -> str:
7 | return '@dataclass\nclass TrainingConfig:\n ' + '\n '.join(loop_advanced_component(advanced_component))
8 |
9 | if __name__ == '__main__':
10 | print(to_classe(init_advanced(flux_train_network.setup_parser())))
--------------------------------------------------------------------------------
/backend/tools/wan.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPTPATH=$(dirname $(realpath ${BASH_SOURCE[0]}))
6 |
7 | export PYTHONPATH=${SCRIPTPATH}/../musubi-tuner
8 | source ${PYTHONPATH}/venv/bin/activate
9 |
10 | cd ${PYTHONPATH}/
11 |
12 | python ${SCRIPTPATH}/wan_args_to_parameter.py
--------------------------------------------------------------------------------
/backend/tools/wan_args_to_parameter.py:
--------------------------------------------------------------------------------
1 | from args2models import init_advanced, loop_advanced_component
2 | from hv_train_network import setup_parser_common
3 | from wan_train_network import wan_setup_parser
4 | from typing import List
5 |
6 |
7 |
8 |
9 | def to_classe(advanced_component: List) -> str:
10 | return '@dataclass\nclass WanI2VTrainingConfig:\n ' + '\n '.join(loop_advanced_component(advanced_component))
11 |
12 | if __name__ == '__main__':
13 | print(to_classe(init_advanced(wan_setup_parser(setup_parser_common()))))
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
2 | charset = utf-8
3 | indent_size = 2
4 | indent_style = tab
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 |
--------------------------------------------------------------------------------
/frontend/.env.example:
--------------------------------------------------------------------------------
1 | # 本地环境 development | production
2 | NODE_ENV="production"
3 | # 标题
4 | VITE_APP_TITLE="智灵训练器"
5 | # baseURL
6 | VITE_APP_BASE_URL="/admin"
7 | # 本地持久化key前缀
8 | VITE_APP_LOCAL_KEY_PREFIX="__spirit-lora-trainer__"
9 | # api请求地址
10 | VITE_APP_API_BASE_URL="/api"
11 | # 开启小白校验
12 | VITE_APP_WHITE_CHECK="false"
13 | # lora输出路径前缀要求
14 | VITE_APP_LORA_OUTPUT_PARENT_PATH="/root"
15 | # wan视频训练max_frames
16 | VITE_APP_WAN_VIDEO_MAX_FRAMES="129"
--------------------------------------------------------------------------------
/frontend/.env.production:
--------------------------------------------------------------------------------
1 | # 生产环境
2 | NODE_ENV="production"
3 | # 标题
4 | VITE_APP_TITLE="智灵训练器"
5 | # baseURL
6 | VITE_APP_BASE_URL="/admin"
7 | # 本地持久化key前缀
8 | VITE_APP_LOCAL_KEY_PREFIX="__spirit-lora-trainer__"
9 | # api请求地址
10 | VITE_APP_API_BASE_URL="/api"
11 | # 开启小白校验
12 | VITE_APP_WHITE_CHECK="false"
13 | # lora输出路径前缀要求
14 | VITE_APP_LORA_OUTPUT_PARENT_PATH="/root"
15 | # wan视频训练max_frames
16 | VITE_APP_WAN_VIDEO_MAX_FRAMES="129"
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
--------------------------------------------------------------------------------
/frontend/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | html/*
3 | .local
4 | node_modules/**
5 | **/*.svg
6 | **/*.sh
7 | public/*
8 |
--------------------------------------------------------------------------------
/frontend/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "semi": true,
4 | "useTabs": true,
5 | "tabWidth": 2,
6 | "singleQuote": false,
7 | "printWidth": 100,
8 | "trailingComma": "none",
9 | "endOfLine": "lf"
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "dbaeumer.vscode-eslint",
5 | "EditorConfig.EditorConfig",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "explorer.fileNesting.enabled": true,
3 | "explorer.fileNesting.patterns": {
4 | "tsconfig.json": "tsconfig.*.json, env.d.ts",
5 | "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
6 | "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .prettier*, prettier*, .editorconfig"
7 | },
8 | "editor.codeActionsOnSave": {
9 | "source.fixAll": "explicit"
10 | },
11 | "editor.formatOnSave": true,
12 | "editor.defaultFormatter": "esbenp.prettier-vscode",
13 | "cSpell.words": [
14 | "AIGC",
15 | "Axios",
16 | "dotdotdot",
17 | "friframeder",
18 | "huggingface",
19 | "hunyuan",
20 | "logit",
21 | "mulingyuer",
22 | "oxlint",
23 | "pinia",
24 | "popconfirm",
25 | "sdxl",
26 | "shiki",
27 | "VITE"
28 | ],
29 | "editor.wordWrap": "on",
30 | "[typescript]": {
31 | "editor.defaultFormatter": "esbenp.prettier-vscode"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # 智灵训练器
2 |
3 | Spirit Lora Trainer 是一款功能强大的工具,旨在提供简单且可靠的方式来训练 Flux1-LoRA 模型。它基于 kohya-ss script 构建,拥有简洁直观的用户界面,能够有效简化模型训练过程,同时提供实时监控功能,其分离架构确保了训练过程的稳定性。训练器支持基本的训练工作流程,包括模型训练和图像打标。
4 |
5 | ## 环境变量
6 |
7 | ### 开发模式
8 |
9 | 需要在项目根目录创建 `.env.development` 文件,并在其中添加`.env.example`示例文件中定义的环境变量,其中 `NODE_ENV` 变量需要设置为 `development`
10 |
11 | 示例:
12 |
13 | ```env
14 | # 本地环境
15 | NODE_ENV="development"
16 |
17 | ```
18 |
19 | ### 生产模式
20 |
21 | 默认情况下已经设置好了生产环境的环境变量,无需额外设置。如果有定制需求可以自行修改 `.env.production` 文件。
22 |
23 | ### 自定义环境变量文件
24 |
25 | 如果需要使用自定义环境变量文件,需要使用`-mode xxx`命令模式,比如我们使用`build`命令打包:
26 |
27 | ```bash
28 | pnpm run build -mode aaa
29 | ```
30 |
31 | 这就表示build命令需要使用`.env.aaa`环境变量文件。
32 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 智灵 LoRA 训练器
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
通过 Serverless 扩展机器学习
17 |
快速运行您的AI模型,并在需要时自动扩展
18 |
19 |
训练器加载中
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/images/loading_bg_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/public/images/loading_bg_dark.png
--------------------------------------------------------------------------------
/frontend/public/images/loading_bg_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/public/images/loading_bg_light.png
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/frontend/src/api/lora/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-20 09:32:40
4 | * @LastEditTime: 2025-04-07 10:01:11
5 | * @LastEditors: mulingyuer
6 | * @Description: lora api
7 | * @FilePath: \frontend\src\api\lora\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | import { request } from "@/request";
12 | import type {
13 | StartFluxTrainingData,
14 | StartFluxTrainingResult,
15 | StartHyVideoTrainingData,
16 | StartHyVideoTrainingResult,
17 | StartWanVideoTrainingData,
18 | StartWanVideoTrainingResult,
19 | WanVideoVideoDatasetEstimateData,
20 | WanVideoVideoDatasetEstimateResult
21 | } from "./types";
22 | export type * from "./types";
23 |
24 | /** 启动flux训练 */
25 | export function startFluxTraining(data: StartFluxTrainingData) {
26 | return request({
27 | url: "/training/start",
28 | method: "POST",
29 | data
30 | });
31 | }
32 |
33 | /** 启动混元视频训练 */
34 | export function startHyVideoTraining(data: StartHyVideoTrainingData) {
35 | return request({
36 | url: "/training/hunyuan/start",
37 | method: "POST",
38 | data
39 | });
40 | }
41 |
42 | /** 启动wan视频训练 */
43 | export function startWanVideoTraining(data: StartWanVideoTrainingData) {
44 | return request({
45 | url: "/training/wan/start",
46 | method: "POST",
47 | data
48 | });
49 | }
50 |
51 | /** 获取wan视频素材训练集提取的图片帧数量 */
52 | export function wanVideoVideoDatasetEstimate(data: WanVideoVideoDatasetEstimateData) {
53 | return request({
54 | url: "/training/wan/datasets/estimate",
55 | method: "POST",
56 | data
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/frontend/src/api/tag/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-19 17:32:51
4 | * @LastEditTime: 2024-12-20 09:21:34
5 | * @LastEditors: mulingyuer
6 | * @Description: 打标api
7 | * @FilePath: \frontend\src\api\tag\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { request } from "@/request";
11 | import type {
12 | BatchTagData,
13 | BatchTagResult,
14 | DeleteFileParams,
15 | ManualTagData,
16 | ManualTagResult
17 | } from "./types";
18 | export type * from "./types";
19 |
20 | /** 一键打标 */
21 | export function batchTag(data: BatchTagData) {
22 | return request({
23 | url: "/training/tag",
24 | method: "POST",
25 | data
26 | });
27 | }
28 |
29 | /** 手动打标 */
30 | export function manualTag(data: ManualTagData) {
31 | return request({
32 | url: "/training/tag_manual",
33 | method: "POST",
34 | data
35 | });
36 | }
37 |
38 | /** 删除文件 */
39 | export function deleteFile(params: DeleteFileParams) {
40 | return request({
41 | url: "/delete_file",
42 | method: "DELETE",
43 | params
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/api/tag/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-19 17:33:00
4 | * @LastEditTime: 2025-02-08 10:10:59
5 | * @LastEditors: mulingyuer
6 | * @Description: 打标api类型
7 | * @FilePath: \frontend\src\api\tag\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 一键打标参数 */
12 | export interface BatchTagData {
13 | /** 模型名称,用于选择相应的打标方式 */
14 | model_name: string;
15 | /** 要打标的图片文件夹路径 */
16 | image_path: string;
17 | /** joy-caption-alpha-two打标模型的具体打标类型 */
18 | prompt_type?: string;
19 | /** 是否把触发词输出到打标文件中 */
20 | class_token?: string;
21 | /** 打标提示词 */
22 | global_prompt: string;
23 | /** 是否追加到已有打标文件中 */
24 | is_append: boolean;
25 | }
26 |
27 | /** 一键打标参数响应 */
28 | export interface BatchTagResult {
29 | /** 任务id */
30 | task_id: string;
31 | }
32 |
33 | /** 手动打标参数 */
34 | export interface ManualTagData {
35 | /** 打标图片的完整路径 */
36 | image_path: string;
37 | /** 图片标注文本 */
38 | caption_text: string;
39 | }
40 |
41 | /** 手动打标参数响应 */
42 | export interface ManualTagResult {
43 | caption_text: string;
44 | image_path: string;
45 | txt_path: string;
46 | }
47 |
48 | /** 删除文件参数 */
49 | export interface DeleteFileParams {
50 | /** 需要删除的文件的完整路径 */
51 | file_path: string;
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/api/task/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-24 16:53:10
4 | * @LastEditTime: 2025-04-02 15:55:09
5 | * @LastEditors: mulingyuer
6 | * @Description: 任务api
7 | * @FilePath: \frontend\src\api\task\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { request } from "@/request";
11 | import type {
12 | CurrentTaskFormConfigResult,
13 | CurrentTaskParams,
14 | CurrentTaskResult,
15 | TaskListResult,
16 | TaskLogParams,
17 | TaskLogResult
18 | } from "./types";
19 | export type * from "./types";
20 |
21 | /** 查询当前正在运行的任务 */
22 | export function currentTask(timeout?: number) {
23 | return request({
24 | url: "/tasks/current",
25 | method: "GET",
26 | showErrorMessage: false,
27 | timeout
28 | });
29 | }
30 |
31 | /** 获取任务列表 */
32 | export function taskList() {
33 | return request({
34 | url: "/tasks/history",
35 | method: "GET"
36 | });
37 | }
38 |
39 | /** 获取当前训练任务的表单配置 */
40 | export function currentTaskFormConfig(params: CurrentTaskParams) {
41 | return request({
42 | url: "/tasks/history",
43 | method: "GET",
44 | params,
45 | showErrorMessage: false
46 | });
47 | }
48 |
49 | /** 获取训练任务日志 */
50 | export function taskLog(params: TaskLogParams) {
51 | return request({
52 | url: "/tasks/logs",
53 | method: "GET",
54 | params
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/frontend/src/api/task/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-24 17:02:02
4 | * @LastEditTime: 2025-04-02 15:54:18
5 | * @LastEditors: mulingyuer
6 | * @Description: 任务api类型
7 | * @FilePath: \frontend\src\api\task\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { TaskStatus, TaskType } from "../types";
11 | import type {
12 | ManualTagInfoResult,
13 | LoRATrainingInfoResult,
14 | HyVideoTrainingInfoResult
15 | } from "@/api/monitor/types";
16 |
17 | /** 查询当前正在运行的任务响应值 */
18 | export interface CurrentTaskResult {
19 | /** 任务id */
20 | id: string;
21 | /** 任务状态 */
22 | status: TaskStatus;
23 | /** 任务类型*/
24 | task_type: TaskType;
25 | /** 任务详情,有可能是空对象 */
26 | detail: any;
27 | }
28 |
29 | /** 获取任务列表 */
30 | export type TaskListResult = Array<
31 | ManualTagInfoResult | LoRATrainingInfoResult | HyVideoTrainingInfoResult
32 | >;
33 |
34 | /** 获取当前训练任务的表单配置参数 */
35 | export interface CurrentTaskParams {
36 | /** 任务id */
37 | task_id: string;
38 | /** 是否获取训练配置 */
39 | show_config: boolean;
40 | }
41 |
42 | /** 获取当前训练任务的表单配置结果 */
43 | export interface CurrentTaskFormConfigResult {
44 | /** 任务 ID */
45 | id: string;
46 | /** 任务状态 */
47 | status: TaskStatus;
48 | /** 训练器的训练配置 */
49 | frontend_config: string;
50 | }
51 |
52 | /** 获取训练任务日志参数 */
53 | export interface TaskLogParams {
54 | /** 任务id */
55 | task_id: string;
56 | }
57 |
58 | /** 获取训练任务日志结果 */
59 | export type TaskLogResult = Array;
60 |
--------------------------------------------------------------------------------
/frontend/src/api/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-25 10:20:37
4 | * @LastEditTime: 2025-03-31 08:41:12
5 | * @LastEditors: mulingyuer
6 | * @Description: 通用类型
7 | * @FilePath: \frontend\src\api\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 任务状态
12 | * - created: 启动中,打标或训练启动中
13 | * - running: 运行中,打标或训练运行中
14 | * - failed: 失败,打标或训练失败
15 | * - complete: 完成,打标或训练完成
16 | */
17 | export type TaskStatus = "complete" | "created" | "running" | "failed";
18 |
19 | /** 任务类型
20 | * - captioning: 打标
21 | * - training: flux 训练
22 | */
23 | export enum TaskType {
24 | /** 打标 */
25 | CAPTIONING = "captioning",
26 | /** flux训练 */
27 | TRAINING = "training",
28 | /** 混元视频训练 */
29 | HUNYUAN_TRAINING = "hunyuan_training",
30 | /** wan视频训练 */
31 | WAN_TRAINING = "wan_training"
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/about/service_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/about/service_dark.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/about/service_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/about/service_light.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/about/wx_gzh_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/about/wx_gzh_dark.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/about/wx_gzh_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/about/wx_gzh_light.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/ai-dataset/image_icon.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/ai-dataset/text_icon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/0.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/1.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/10.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/11.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/12.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/13.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/14.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/15.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/16.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/17.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/18.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/19.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/2.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/20.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/21.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/22.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/23.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/24.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/25.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/26.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/27.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/28.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/29.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/3.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/4.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/5.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/6.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/7.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/8.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/favicon/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/favicon/9.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/logo_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/logo_light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/user-avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/frontend/src/assets/images/user-avatar.jpg
--------------------------------------------------------------------------------
/frontend/src/components/AiDataset/DatasetPagination.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
22 |
23 |
24 |
52 |
53 |
59 |
--------------------------------------------------------------------------------
/frontend/src/components/AiDataset/context-menu.helper.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-13 17:41:25
4 | * @LastEditTime: 2025-04-02 11:31:50
5 | * @LastEditors: mulingyuer
6 | * @Description: 右键菜单帮助工具
7 | * @FilePath: \frontend\src\components\AiDataset\context-menu.helper.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { FileItem } from "@/utils/file-manager";
11 | import { FileType } from "@/utils/file-manager";
12 |
13 | export enum ContextMenuKeyEnum {
14 | /** 编辑 */
15 | EDIT = "edit",
16 | /** 打标 */
17 | TAG = "tag",
18 | /** 删除 */
19 | DELETE = "delete"
20 | }
21 |
22 | export interface ContextMenuItem {
23 | label: string;
24 | key: ContextMenuKeyEnum;
25 | /** 是否显示 */
26 | show: boolean;
27 | /** 禁用 */
28 | disabled: boolean;
29 | }
30 | export type ContextMenu = ContextMenuItem[];
31 |
32 | /** 菜单数据 */
33 | export const menuList = ref([
34 | {
35 | label: "编辑",
36 | key: ContextMenuKeyEnum.EDIT,
37 | show: true,
38 | disabled: false
39 | },
40 | {
41 | label: "手动打标",
42 | key: ContextMenuKeyEnum.TAG,
43 | show: true,
44 | disabled: false
45 | },
46 | {
47 | label: "删除",
48 | key: ContextMenuKeyEnum.DELETE,
49 | show: true,
50 | disabled: false
51 | }
52 | ]);
53 |
54 | export function updateMenuList(data: FileItem) {
55 | // 先重置状态
56 | menuList.value.forEach((item) => {
57 | item.show = true;
58 | item.disabled = false;
59 | });
60 |
61 | // 根据文件类型更新菜单
62 | switch (data.type) {
63 | case FileType.IMAGE:
64 | case FileType.VIDEO:
65 | menuList.value[0].show = false;
66 | break;
67 | case FileType.TEXT:
68 | menuList.value[1].show = false;
69 | break;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/frontend/src/components/AiDataset/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-13 15:25:58
4 | * @LastEditTime: 2025-04-02 11:32:24
5 | * @LastEditors: mulingyuer
6 | * @Description: AI数据集类型
7 | * @FilePath: \frontend\src\components\AiDataset\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | // /** 文件组件基础props */
11 | export interface BaseFileItemProps {
12 | /** 是否选中 */
13 | selected: boolean;
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/components/Card/Card.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/frontend/src/components/Dialog/LoraTaskLogDialog.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/src/components/Dialog/NetworkRetryDialog.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
18 |
19 |
您可以尝试以下操作:
20 |
21 | - 检查您的网络连接是否正常,并确保能够访问互联网;
22 | -
23 | 若网络正常,请耐心等待页面自动重新连接,或者前往 GPU
24 | 详情页重新打开训练器页面以查看训练进度;
25 |
26 |
27 |
28 |
29 |
30 |
31 |
45 |
46 |
56 |
--------------------------------------------------------------------------------
/frontend/src/components/FieldSetWrapper/FieldSetWrapper.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
56 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/FileItem/ImageFile.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
{{ data.name }}
27 |
28 |
29 |
30 |
48 |
49 |
52 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/FileItem/TextFile.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
{{ data.name }}
14 |
15 |
16 |
17 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/FileItem/VideoFile.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
{{ data.name }}
27 |
28 |
29 |
30 |
48 |
49 |
52 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-02 11:40:32
4 | * @LastEditTime: 2025-04-02 11:41:32
5 | * @LastEditors: mulingyuer
6 | * @Description: 文件管理器入口
7 | * @FilePath: \frontend\src\components\FileManager\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { FileType } from "@/utils/file-manager";
11 | import ImageFile from "./FileItem/ImageFile.vue";
12 | import TextFile from "./FileItem/TextFile.vue";
13 | import VideoFile from "./FileItem/VideoFile.vue";
14 |
15 | /** 组件map */
16 | export const FileItemMap: Record = {
17 | [FileType.IMAGE]: ImageFile,
18 | [FileType.TEXT]: TextFile,
19 | [FileType.VIDEO]: VideoFile
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/style.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/variables.scss" as *;
2 | @use "@/styles/mixins.scss" as *;
3 |
4 | // 统一样式
5 | .file-list-item {
6 | width: $zl-ai-dataset-file-width;
7 | height: $zl-ai-dataset-file-height;
8 | text-align: center;
9 | transition:
10 | background-color 0.3s ease,
11 | opacity 0.3s ease;
12 | border-radius: 4px;
13 | padding: 4px;
14 | cursor: pointer;
15 | @include no-select();
16 | &.selected {
17 | background-color: var(--zl-admin-layout-bg);
18 | }
19 | &:hover {
20 | background-color: var(--zl-admin-layout-bg);
21 | }
22 | &:active {
23 | opacity: 0.7;
24 | }
25 | }
26 |
27 | .file-list-item-img {
28 | width: 50px;
29 | height: 50px;
30 | border-radius: 4px;
31 | }
32 |
33 | .file-list-item-default-img {
34 | width: 100%;
35 | height: 100%;
36 | object-fit: contain;
37 | }
38 |
39 | .file-list-item-name {
40 | margin-top: 4px;
41 | font-size: 12px;
42 | line-height: 20px;
43 | color: var(--el-text-color-primary);
44 | @include text-ellipsis();
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/components/FileManager/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-02 11:39:59
4 | * @LastEditTime: 2025-04-02 11:45:48
5 | * @LastEditors: mulingyuer
6 | * @Description: 文件管理器的类型
7 | * @FilePath: \frontend\src\components\FileManager\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 文件组件基础props */
12 | export interface BaseFileItemProps {
13 | /** 是否选中 */
14 | selected?: boolean;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/BaseSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagAddGlobalPromptSwitch.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
36 |
37 |
42 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagAdvancedSwitch.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
36 |
37 |
42 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagAppendSwitch.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagDirectory.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagGlobalPrompt.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagJoyCaptionPrompt.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagModelSelect.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagResetButton.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
21 |
22 | {{ btnText }}
23 |
24 |
25 |
26 |
27 |
57 |
58 |
69 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/DataSet-v2/TagSubmitButton.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
21 |
22 | {{ btnText }}
23 |
24 |
25 |
26 |
27 |
57 |
58 |
69 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/FileSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/FluxNetworkModuleSelect.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/FolderSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/LrSchedulerSelect.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/ModelSaveFormatSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/ModelSavePrecisionSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/PopoverFormItem.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 | {{ label }}
15 |
20 | <{{ popoverContent }}>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
45 |
46 |
64 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/SDXLNetworkModuleSelect.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/WeightingSchemeSelect.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/frontend/src/components/GlobalModalManager/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/frontend/src/components/Icon/Icon.vue:
--------------------------------------------------------------------------------
1 |
10 |
61 |
--------------------------------------------------------------------------------
/frontend/src/directives/complexity/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 16:42:02
4 | * @LastEditTime: 2024-12-04 17:08:35
5 | * @LastEditors: mulingyuer
6 | * @Description: 难易度指令
7 | * @FilePath: \frontend\src\directives\complexity\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { CustomDirective } from "../types";
11 | import { useSettingsStore } from "@/stores";
12 |
13 | export const complexityDirective: CustomDirective = {
14 | name: "complexity",
15 | directive: {
16 | mounted: (el, binding) => {
17 | const settings = useSettingsStore();
18 | const { complexity } = storeToRefs(settings);
19 |
20 | function updateVisibility() {
21 | const shouldShow = complexity.value === binding.value;
22 | el.style.display = shouldShow ? "" : "none";
23 | }
24 |
25 | // 初始化显示状态
26 | updateVisibility();
27 |
28 | // 监听 Pinia store 的变化
29 | watch(complexity, () => {
30 | updateVisibility();
31 | });
32 | }
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/frontend/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 17:00:59
4 | * @LastEditTime: 2024-12-04 17:02:09
5 | * @LastEditors: mulingyuer
6 | * @Description: 自定义指令入口
7 | * @FilePath: \frontend\src\directives\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { App } from "vue";
11 | import { complexityDirective } from "./complexity";
12 |
13 | const directives = [complexityDirective];
14 |
15 | export default {
16 | install(app: App) {
17 | directives.forEach((directive) => {
18 | app.directive(directive.name, directive.directive);
19 | });
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/frontend/src/directives/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 16:44:53
4 | * @LastEditTime: 2024-12-04 17:01:58
5 | * @LastEditors: mulingyuer
6 | * @Description: 自定义指令类型
7 | * @FilePath: \frontend\src\directives\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { Directive } from "vue";
11 |
12 | export interface CustomDirective {
13 | /** 指令名称 */
14 | name: string;
15 | /** 指令 */
16 | directive: Directive;
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/enums/complexity.enum.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 17:15:04
4 | * @LastEditTime: 2024-12-04 17:15:04
5 | * @LastEditors: mulingyuer
6 | * @Description: 难易度枚举
7 | * @FilePath: \frontend\src\enums\complexity.enum.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 表单复杂度枚举 */
12 | export enum ComplexityEnum {
13 | /** 新手 */
14 | BEGINNER = "beginner",
15 | /** 专家 */
16 | EXPERT = "expert"
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/enums/split-right.enum.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-12 15:48:24
4 | * @LastEditTime: 2024-12-12 15:48:24
5 | * @LastEditors: mulingyuer
6 | * @Description: split-right.enum.ts
7 | * @FilePath: \frontend\src\enums\split-right.enum.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | export enum SplitRightEnum {
12 | /** AI数据集 */
13 | AI_DATASET = "ai_dataset",
14 | /** TOML预览 */
15 | TOML_PREVIEW = "toml_preview"
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/hooks/task/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-11 10:06:22
4 | * @LastEditTime: 2025-04-11 15:45:45
5 | * @LastEditors: mulingyuer
6 | * @Description: 监听公共类型
7 | * @FilePath: \frontend\src\hooks\task\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 任务状态 */
12 | export type TaskStatus =
13 | | "idle" // 空闲
14 | | "querying" // 查询中
15 | | "paused" // 暂停
16 | | "success" // 成功
17 | | "failure"; // 失败
18 |
19 | /** 任务事件订阅 */
20 | export type TaskEvents = {
21 | update: void;
22 | complete: void;
23 | failed: void;
24 | };
25 |
26 | /** 任务实现接口 */
27 | export interface TaskImplementation {
28 | /** 开始 */
29 | start(): void;
30 |
31 | /** 暂停 */
32 | pause(): void;
33 |
34 | /** 继续 */
35 | resume(): void;
36 |
37 | /** 停止 */
38 | stop(): void;
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAuth.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-30 10:01:54
4 | * @LastEditTime: 2025-03-21 08:55:47
5 | * @LastEditors: mulingyuer
6 | * @Description: 登录退出通用处理hooks
7 | * @FilePath: \frontend\src\hooks\useAuth.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { useUserStore } from "@/stores";
11 |
12 | export function useAuth() {
13 | const userStore = useUserStore();
14 |
15 | /** 登录通用处理 */
16 | async function login(token: string) {
17 | userStore.setToken(token);
18 | }
19 |
20 | /** 退出通用处理 */
21 | async function logout() {
22 | userStore.clearToken();
23 | }
24 |
25 | return {
26 | login,
27 | logout
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useEnhancedStorage.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-30 11:31:58
4 | * @LastEditTime: 2024-12-30 11:50:37
5 | * @LastEditors: mulingyuer
6 | * @Description: 自定义useLocalStorage
7 | * @FilePath: \frontend\src\hooks\useEnhancedStorage.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | import type { RemovableRef } from "@vueuse/core";
12 |
13 | export function useEnhancedStorage() {
14 | /** 自定义useLocalStorage
15 | * 支持当缓存中的数据缺少字段时,自动从默认对象中补充
16 | */
17 | function useEnhancedLocalStorage(localKey: string, defaultValue: T): RemovableRef {
18 | return useLocalStorage(localKey, defaultValue, {
19 | serializer: {
20 | read(raw) {
21 | try {
22 | const obj = raw !== null ? JSON.parse(raw) : null;
23 | if (!obj) return defaultValue;
24 | if (Object.prototype.toString.call(obj) !== "[object Object]") return obj;
25 | // 检查是否缺少字段
26 | const localObjKeys = new Set(Object.keys(obj));
27 | const defaultObjKeys = new Set(Object.keys(defaultValue as Record));
28 | const missingKeys = [...defaultObjKeys].filter((key) => !localObjKeys.has(key));
29 | missingKeys.forEach((key) => {
30 | // @ts-expect-error 去除ts类型警告
31 | obj[key] = defaultValue[key];
32 | });
33 |
34 | return obj;
35 | } catch (error) {
36 | console.error("解析缓存的数据失败", error);
37 | return null;
38 | }
39 | },
40 | write: (value) => JSON.stringify(value)
41 | }
42 | });
43 | }
44 |
45 | return {
46 | useEnhancedLocalStorage
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useIcon.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-10-11 17:11:35
4 | * @LastEditTime: 2024-10-11 17:16:21
5 | * @LastEditors: mulingyuer
6 | * @Description: 函数式组件中使用icon的hook
7 | * @FilePath: \element-admin-template\src\hooks\useIcon.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import Icon from "@/components/Icon/Icon.vue";
11 |
12 | export interface UseIconProps {
13 | /** 图标名称 */
14 | name: string;
15 | /** 图标大小,默认16 */
16 | size?: string | number;
17 | /** 图标颜色,默认继承父元素颜色 */
18 | color?: string;
19 | }
20 |
21 | export function useIcon(props: UseIconProps) {
22 | return h(Icon, { ...props });
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useImageViewer.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-16 09:50:57
4 | * @LastEditTime: 2025-02-08 09:16:33
5 | * @LastEditors: mulingyuer
6 | * @Description: 图片预览
7 | * @FilePath: \frontend\src\hooks\useImageViewer.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { createVNode, render } from "vue";
11 | import { ElImageViewer } from "element-plus";
12 | import type { ImageViewerProps } from "element-plus";
13 |
14 | export type PreviewOption = Partial & {
15 | filenameList?: string[];
16 | };
17 |
18 | export function useImageViewer() {
19 | /** 图片预览 */
20 | function previewImages({ filenameList, ...options }: PreviewOption) {
21 | const container = document.createElement("div");
22 | let vmDom: Element | null = null;
23 | const currentIndex = ref(options.initialIndex ?? 0);
24 |
25 | const vm = createVNode(
26 | ElImageViewer,
27 | {
28 | ...options,
29 | onClose() {
30 | render(null, container);
31 | vmDom && document.body.removeChild(vmDom);
32 | container.remove();
33 | },
34 | onSwitch(newIndex: number) {
35 | currentIndex.value = newIndex;
36 | }
37 | },
38 | {
39 | default: () => {
40 | if (!filenameList) return null;
41 | const filename = filenameList[currentIndex.value] ?? "";
42 | return filename
43 | ? h(
44 | "div",
45 | {
46 | class: "el-image-viewer__file_name"
47 | },
48 | filename
49 | )
50 | : null;
51 | }
52 | }
53 | );
54 |
55 | // 将组件渲染成真实节点
56 | render(vm, container);
57 | vmDom = container.firstElementChild!;
58 | document.body.appendChild(vmDom);
59 | }
60 |
61 | return {
62 | previewImages
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useRedirect.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 15:40:01
4 | * @LastEditTime: 2025-03-07 17:12:15
5 | * @LastEditors: mulingyuer
6 | * @Description: 重定向跳转hooks
7 | * @FilePath: \frontend\src\hooks\useRedirect.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | // useRedirect.ts
11 | import type { RouteLocationRaw } from "vue-router";
12 |
13 | export type RedirectMethod = "push" | "replace";
14 |
15 | export function useRedirect() {
16 | const router = useRouter();
17 | const route = useRoute();
18 |
19 | /** 重定向跳转 */
20 | function handleRedirect(method: RedirectMethod, to: RouteLocationRaw) {
21 | const redirectStr = route.query.redirect;
22 |
23 | if (redirectStr) {
24 | const { redirectPath, redirectQuery } = parseRedirect(redirectStr as string);
25 | const { redirect: _redirect, ...otherQuery } = route.query;
26 | return router[method]({
27 | path: redirectPath,
28 | query: {
29 | ...otherQuery,
30 | ...redirectQuery
31 | }
32 | });
33 | }
34 |
35 | return router[method](to);
36 | }
37 |
38 | /** 解析redirect字符串 */
39 | function parseRedirect(redirect: string) {
40 | const url = new URL(redirect, window.location.origin);
41 | const redirectPath = url.pathname;
42 | // @ts-expect-error 类型错误
43 | const redirectQuery = Object.fromEntries(url.searchParams.entries());
44 | return {
45 | redirectPath,
46 | redirectQuery
47 | };
48 | }
49 |
50 | return {
51 | push: (to: RouteLocationRaw) => handleRedirect("push", to),
52 | replace: (to: RouteLocationRaw) => handleRedirect("replace", to),
53 | go: router.go.bind(router),
54 | back: router.back.bind(router),
55 | forward: router.forward.bind(router),
56 | $router: router
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useVideoPreview.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-01 16:48:50
4 | * @LastEditTime: 2025-04-01 17:27:24
5 | * @LastEditors: mulingyuer
6 | * @Description: 视频预览的hook
7 | * @FilePath: \frontend\src\hooks\useVideoPreview.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { useModalManagerStore } from "@/stores";
11 | import type { VideoPreviewModal } from "@/stores";
12 |
13 | export type PreviewVideo = Omit;
14 |
15 | export function useVideoPreview() {
16 | const modelManagerStore = useModalManagerStore();
17 | const videoPreviewModal = storeToRefs(modelManagerStore).videoPreviewModal;
18 |
19 | /** 视频预览 */
20 | function previewVideo(data: PreviewVideo) {
21 | // 如果已有实例,先关闭
22 | if (videoPreviewModal.value.open) {
23 | modelManagerStore.resetVideoPreviewModal();
24 | }
25 |
26 | // 打开新的实例
27 | modelManagerStore.setVideoPreviewModal({
28 | ...data,
29 | open: true
30 | });
31 | }
32 |
33 | /** 关闭视频预览 */
34 | function closePreviewVideo() {
35 | modelManagerStore.resetVideoPreviewModal();
36 | }
37 |
38 | return {
39 | previewVideo,
40 | closePreviewVideo
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/init-lora-trainer/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-01-09 14:54:12
4 | * @LastEditTime: 2025-01-09 15:12:30
5 | * @LastEditors: mulingyuer
6 | * @Description: 初始化训练器
7 | * @FilePath: \frontend\src\init-lora-trainer\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { initTask } from "./task";
11 | import { initAnimatedFavicon } from "./animated-favicon";
12 |
13 | export function initLoraTrainer() {
14 | return Promise.allSettled([initTask(), initAnimatedFavicon()]);
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/components/Aside/Footer/MenuCollapse.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
19 |
20 |
21 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/components/Aside/Footer/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
21 |
22 |
23 |
27 |
28 |
46 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/components/Aside/Logo.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 | {{ logoTitle }}
15 |
16 |
17 |
18 |
19 |
26 |
27 |
52 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/components/Aside/Menu.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
24 |
25 |
26 |
27 |
57 |
58 |
65 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/components/Aside/MenuItem.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 | {{ menu.title }}
15 |
16 |
17 |
18 |
19 |
20 | {{ menu.title }}
21 |
22 |
23 |
24 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/frontend/src/layout/admin-layout/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
32 |
--------------------------------------------------------------------------------
/frontend/src/layout/blank-layout/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import "default-passive-events"; //去除touchstart谷歌警告
2 | import { createApp } from "vue";
3 | import { piniaStore } from "@/stores";
4 | import App from "./App.vue";
5 | import { setupRouter } from "./router";
6 | import VueFinder from "vuefinder/dist/vuefinder";
7 | import { initLoraTrainer } from "./init-lora-trainer";
8 |
9 | // style
10 | import "@/styles/index.scss";
11 | import "vuefinder/dist/style.css";
12 |
13 | // plugins
14 | import { ElementPlusPlugin } from "@/plugins/element-plus";
15 |
16 | // directives
17 | import directives from "@/directives";
18 |
19 | async function setupApp() {
20 | const app = createApp(App);
21 |
22 | app.use(ElementPlusPlugin);
23 | app.use(piniaStore);
24 | app.use(directives);
25 | app.use(VueFinder);
26 | await setupRouter(app);
27 |
28 | // 初始化
29 | await initLoraTrainer();
30 |
31 | app.mount("#app");
32 | }
33 |
34 | setupApp();
35 |
--------------------------------------------------------------------------------
/frontend/src/plugins/element-plus.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 14:53:53
4 | * @LastEditTime: 2024-10-15 11:53:46
5 | * @LastEditors: mulingyuer
6 | * @Description: element-plus
7 | * @FilePath: \element-admin-template\src\plugins\element-plus.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import * as ElementPlusIcons from "@element-plus/icons-vue";
11 | import type { App } from "vue";
12 | import "element-plus/theme-chalk/display.css";
13 | import { ElTable } from "element-plus";
14 |
15 | /** 定制化element-plus组件 */
16 | function customElementPlus(app: App) {
17 | // table
18 | const TableProps = ElTable.props;
19 | TableProps.stripe = { type: Boolean, default: true };
20 | TableProps.size = { type: String, default: "large" };
21 |
22 | const components = [ElTable];
23 | components.forEach((component) => {
24 | app.use(component);
25 | });
26 | }
27 |
28 | export const ElementPlusPlugin = {
29 | install(app: App) {
30 | // 全局注册ElementPlus图标
31 | for (const [key, component] of Object.entries(ElementPlusIcons)) {
32 | app.component(key, component);
33 | }
34 | // 定制化element-plus组件
35 | customElementPlus(app);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/frontend/src/request/axios.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-01-21 09:10:49
4 | * @LastEditTime: 2025-01-21 09:10:49
5 | * @LastEditors: mulingyuer
6 | * @Description: 扩展axios类型
7 | * @FilePath: \frontend\src\request\axios.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import "axios"; // 必须确保模块扩展的上下文是在 Axios 模块内
11 |
12 | declare module "axios" {
13 | interface AxiosRequestConfig {
14 | /** 是否允许失败重试 */
15 | enableRetry?: boolean;
16 | /** 是否显示错误消息弹窗 */
17 | showErrorMessage?: boolean;
18 | /** 取消请求是否显示错误消息,注意:`showErrorMessage` 优先级大于该设置。
19 | * 这个配置你得在cancel的时候设置,例:
20 | * ```typescript
21 | * const CancelToken = axios.CancelToken;
22 | * const source = CancelToken.source();
23 | * source.cancel("取消的原因", { showCancelErrorMessage: false });
24 | * ```
25 | */
26 | showCancelErrorMessage?: boolean;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/request/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 16:18:08
4 | * @LastEditTime: 2025-03-11 09:17:25
5 | * @LastEditors: mulingyuer
6 | * @Description: 请求封装
7 | * @FilePath: \frontend\src\request\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { instance } from "./core";
11 | import type { AxiosRequestConfig } from "axios";
12 | import type { RequestResult } from "./types";
13 | export { isNetworkError } from "./helper";
14 |
15 | /** 请求函数 */
16 | export function request(config: AxiosRequestConfig): Promise {
17 | return instance.request(config).then((response) => {
18 | if (!response?.data) return null;
19 |
20 | const { success, data, message } = response.data as RequestResult;
21 |
22 | // 接口请求成功,但是响应业务是失败的,这里要抛出错误,防止走then处理
23 | if (success === false) throw new Error(message);
24 |
25 | return data;
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/request/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 16:21:59
4 | * @LastEditTime: 2025-01-21 09:22:51
5 | * @LastEditors: mulingyuer
6 | * @Description: 请求类型
7 | * @FilePath: \frontend\src\request\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 请求结果的结构 */
12 | export interface RequestResult {
13 | code: number;
14 | message: string;
15 | success: boolean;
16 | data: T;
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/router/guard.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 17:26:54
4 | * @LastEditTime: 2024-12-13 09:34:30
5 | * @LastEditors: mulingyuer
6 | * @Description: 路由守卫
7 | * @FilePath: \spirit-lora-trainer\frontend\src\router\guard\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { Router } from "vue-router";
11 | import { routerAuthMap, RouterAuthContext } from "./router-auth";
12 | import { NProgress } from "@/utils/nprogress";
13 | import { useAppStore } from "@/stores";
14 | import { initRoutes } from "./helpers";
15 | import { modulesRoutes } from "./route-modules";
16 |
17 | export async function createRouterGuard(router: Router) {
18 | const appStore = useAppStore();
19 |
20 | /** 路由守卫 */
21 | router.beforeEach((to, from, next) => {
22 | /** 进度条 */
23 | NProgress.start();
24 | /** 初始化路由 */
25 | if (!appStore.isInitRoute) {
26 | initRoutes(router, modulesRoutes, appStore);
27 | return next({ path: to.fullPath, replace: true, query: to.query, hash: to.hash });
28 | }
29 |
30 | /** 不存在的页面 */
31 | if (to.name === "NotFound") return next({ name: "NotFound404" });
32 |
33 | /** 记录是否显示footer-bar */
34 | const showFooter = to.meta.showFooter ?? false;
35 | appStore.setShowFooter(showFooter);
36 |
37 | /** 鉴权策略 */
38 | const authType = to.meta.auth ?? "required";
39 | const strategy = routerAuthMap[authType];
40 | if (strategy) {
41 | const routerAuthContext = new RouterAuthContext(to, from, next);
42 | strategy.execute(routerAuthContext);
43 | return;
44 | }
45 |
46 | /** 无鉴权时 */
47 | next();
48 | });
49 |
50 | router.afterEach((_to, _from) => {
51 | /** 进度条 */
52 | NProgress.done();
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from "vue-router";
2 | import { rootRoute, notFoundRoute } from "./routes";
3 | import { createRouterGuard } from "./guard";
4 | import type { App } from "vue";
5 |
6 | const router = createRouter({
7 | history: createWebHashHistory(import.meta.env.BASE_URL),
8 | routes: [rootRoute, notFoundRoute],
9 | scrollBehavior: () => ({ left: 0, top: 0 })
10 | });
11 |
12 | export async function setupRouter(app: App) {
13 | app.use(router);
14 | await createRouterGuard(router);
15 | await router.isReady();
16 | }
17 |
18 | export default router;
19 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/about.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 10:03:14
4 | * @LastEditTime: 2025-02-08 15:36:53
5 | * @LastEditors: mulingyuer
6 | * @Description: 关于路由模块
7 | * @FilePath: \frontend\src\router\route-modules\about.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/about",
14 | name: "About",
15 | component: () => import("@/views/about/index.vue"),
16 | meta: {
17 | title: "关于",
18 | auth: "public",
19 | icon: "ri-question-line",
20 | sort: 150
21 | }
22 | } as RouteRecordRaw;
23 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/ai-dataset.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 09:59:42
4 | * @LastEditTime: 2025-01-09 11:27:27
5 | * @LastEditors: mulingyuer
6 | * @Description: AI数据集路由模块
7 | * @FilePath: \frontend\src\router\route-modules\ai-dataset.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/ai-dataset",
14 | name: "AIDataset",
15 | component: () => import("@/views/ai-dataset/index.vue"),
16 | meta: {
17 | title: "AI数据集",
18 | auth: "public",
19 | icon: "ri-database-2-line",
20 | sort: 30
21 | }
22 | } as RouteRecordRaw;
23 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/iframe.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-02-08 15:28:15
4 | * @LastEditTime: 2025-02-08 16:07:41
5 | * @LastEditors: mulingyuer
6 | * @Description: iframe路由
7 | * @FilePath: \frontend\src\router\route-modules\iframe.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default [
13 | {
14 | path: "/dashboard",
15 | name: "Dashboard",
16 | component: () => import("@/views/iframe/index.vue"),
17 | meta: {
18 | title: "仪表盘",
19 | icon: "ri-dashboard-3-line",
20 | auth: "public",
21 | affix: true,
22 | sort: 10,
23 | iframeLink: (() => {
24 | const path = "/tensorboard/";
25 | const isDev = import.meta.env.MODE === "development";
26 | if (isDev) {
27 | return `${import.meta.env.VITE_APP_API_BASE_URL.replace(/\/api$/, import.meta.env.BASE_URL)}${path}`;
28 | }
29 | return `${location.origin}${path}`;
30 | })()
31 | }
32 | },
33 | {
34 | path: "https://serverless.datastone.cn/sprite/docs/zh/lora-trainer/quick-start",
35 | name: "ZLDocs",
36 | component: () => import("@/views/iframe/index.vue"),
37 | meta: {
38 | sort: 70,
39 | auth: "public",
40 | title: "训练器指南",
41 | icon: "ri-article-line"
42 | }
43 | }
44 | ] as RouteRecordRaw[];
45 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-26 17:46:30
4 | * @LastEditTime: 2024-09-29 09:10:42
5 | * @LastEditors: mulingyuer
6 | * @Description: 模块路由路口
7 | * @FilePath: \spirit-app-microservice-admin\src\router\route-modules\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { getModulesRoutes } from "../helpers";
11 |
12 | const globData: Record = import.meta.glob("./!(index)*.ts", { eager: true });
13 |
14 | export const modulesRoutes = getModulesRoutes(globData);
15 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/lora.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 09:51:44
4 | * @LastEditTime: 2025-03-28 10:18:03
5 | * @LastEditors: mulingyuer
6 | * @Description: lora路由模块
7 | * @FilePath: \frontend\src\router\route-modules\lora.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/lora",
14 | component: () => import("@/layout/admin-layout/index.vue"),
15 | meta: {
16 | auth: "public",
17 | title: "LoRA 训练",
18 | icon: "ri-quill-pen-ai-line",
19 | sort: 20,
20 | showFooter: true
21 | },
22 | children: [
23 | // {
24 | // path: "/lora/sdxl",
25 | // name: "LoRA-SDXL",
26 | // component: () => import("@/views/lora/sdxl/index.vue"),
27 | // meta: {
28 | // title: "SDXL"
29 | // }
30 | // },
31 | {
32 | path: "/lora/flux",
33 | name: "LoRA-Flux",
34 | component: () => import("@/views/lora/flux/index.vue"),
35 | meta: {
36 | title: "Flux",
37 | loRATaskType: "flux"
38 | }
39 | },
40 | {
41 | path: "/lora/hunyuan-video",
42 | name: "LoRA-HunyuanVideo",
43 | component: () => import("@/views/lora/hunyuan-video/index.vue"),
44 | meta: {
45 | title: "混元视频",
46 | loRATaskType: "hunyuan-video"
47 | }
48 | },
49 | {
50 | path: "/lora/wan-video",
51 | name: "LoRA-WanVideo",
52 | component: () => import("@/views/lora/wan-video/index.vue"),
53 | meta: {
54 | title: "Wan2.1",
55 | loRATaskType: "wan-video"
56 | }
57 | }
58 | ]
59 | } as RouteRecordRaw;
60 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/settings.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 10:01:30
4 | * @LastEditTime: 2025-02-08 15:38:02
5 | * @LastEditors: mulingyuer
6 | * @Description: 设置页面路由
7 | * @FilePath: \frontend\src\router\route-modules\settings.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/settings",
14 | name: "Settings",
15 | component: () => import("@/views/settings/index.vue"),
16 | meta: {
17 | title: "设置",
18 | auth: "public",
19 | icon: "ri-settings-4-line",
20 | sort: 50
21 | }
22 | } as RouteRecordRaw;
23 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/task.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-26 11:22:30
4 | * @LastEditTime: 2025-01-09 11:27:00
5 | * @LastEditors: mulingyuer
6 | * @Description: 任务列表
7 | * @FilePath: \frontend\src\router\route-modules\task.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/task",
14 | name: "TaskList",
15 | component: () => import("@/views/task/index.vue"),
16 | meta: {
17 | auth: "public",
18 | title: "任务列表",
19 | icon: "ri-list-check-3",
20 | sort: 40
21 | }
22 | } as RouteRecordRaw;
23 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/website.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-27 10:35:37
4 | * @LastEditTime: 2024-12-27 10:35:37
5 | * @LastEditors: mulingyuer
6 | * @Description: 官网介绍路由模块
7 | * @FilePath: \frontend\src\router\route-modules\website.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "/website",
14 | name: "Website",
15 | component: () => import("@/views/website/index.vue"),
16 | meta: {
17 | auth: "public",
18 | title: "官网介绍",
19 | isHide: true
20 | }
21 | } as RouteRecordRaw;
22 |
--------------------------------------------------------------------------------
/frontend/src/router/route-modules/zl.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-30 14:08:23
4 | * @LastEditTime: 2025-02-08 15:38:06
5 | * @LastEditors: mulingyuer
6 | * @Description: 智灵外链
7 | * @FilePath: \frontend\src\router\route-modules\zl.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 |
12 | export default {
13 | path: "https://serverless.datastone.cn/sprite/app/",
14 | name: "ZL",
15 | component: h("div"),
16 | meta: {
17 | auth: "public",
18 | title: "智灵GPU服务",
19 | icon: "ri-link-m",
20 | sort: 60
21 | }
22 | } as RouteRecordRaw;
23 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/context.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:40:21
4 | * @LastEditTime: 2024-09-27 08:53:07
5 | * @LastEditors: mulingyuer
6 | * @Description: 数据上下文
7 | * @FilePath: \spirit-app-microservice-admin\src\router\router-auth\context.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { useUserStore } from "@/stores";
11 | import type { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
12 |
13 | /** 路由鉴权数据上下文 */
14 | export class RouterAuthContext {
15 | /** 是否已登录 */
16 | isLogin: boolean;
17 |
18 | constructor(
19 | public to: RouteLocationNormalized,
20 | public form: RouteLocationNormalized,
21 | public next: NavigationGuardNext
22 | ) {
23 | const userStore = useUserStore();
24 |
25 | this.isLogin = userStore.isLogin;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:33:40
4 | * @LastEditTime: 2024-09-27 09:03:01
5 | * @LastEditors: mulingyuer
6 | * @Description: 路由鉴权
7 | * @FilePath: \spirit-app-microservice-admin\src\router\router-auth\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { PublicStrategy } from "./strategy/public-strategy";
11 | import { GuestStrategy } from "./strategy/guest-strategy";
12 | import { RequiredStrategy } from "./strategy/required-strategy";
13 | import type { AuthStrategy, AuthType } from "./types";
14 | export type * from "./types";
15 | export * from "./context";
16 |
17 | /** 路由策略map */
18 | export const routerAuthMap: Record = {
19 | public: new PublicStrategy(),
20 | guest: new GuestStrategy(),
21 | required: new RequiredStrategy()
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/strategy/guest-strategy.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:52:42
4 | * @LastEditTime: 2024-12-31 09:13:09
5 | * @LastEditors: mulingyuer
6 | * @Description: 访客路由策略
7 | * @FilePath: \frontend\src\router\router-auth\strategy\guest-strategy.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { RouterAuthContext } from "../context";
11 | import type { AuthStrategy } from "../types";
12 |
13 | export class GuestStrategy implements AuthStrategy {
14 | execute(context: RouterAuthContext): void {
15 | const { isLogin, next } = context;
16 |
17 | if (isLogin) {
18 | next({ path: "/" });
19 | } else {
20 | next();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/strategy/public-strategy.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:51:31
4 | * @LastEditTime: 2024-09-27 08:51:31
5 | * @LastEditors: mulingyuer
6 | * @Description: 公开路由策略
7 | * @FilePath: \spirit-app-microservice-admin\src\router\router-auth\strategy\public-strategy.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { RouterAuthContext } from "../context";
11 | import type { AuthStrategy } from "../types";
12 |
13 | export class PublicStrategy implements AuthStrategy {
14 | execute(context: RouterAuthContext): void {
15 | const { next } = context;
16 | next();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/strategy/required-strategy.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:55:34
4 | * @LastEditTime: 2024-09-27 08:56:44
5 | * @LastEditors: mulingyuer
6 | * @Description: 必须登录的策略
7 | * @FilePath: \spirit-app-microservice-admin\src\router\router-auth\strategy\required-strategy.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { RouterAuthContext } from "../context";
11 | import type { AuthStrategy } from "../types";
12 |
13 | export class RequiredStrategy implements AuthStrategy {
14 | execute(context: RouterAuthContext): void {
15 | const { isLogin, to, next } = context;
16 |
17 | if (!isLogin) {
18 | // 未登录,跳转到登录页面
19 | next({ name: "Login", query: { redirect: to.fullPath } });
20 | } else {
21 | // 已登录,正常访问
22 | next();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/router/router-auth/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 08:48:27
4 | * @LastEditTime: 2024-09-27 08:51:03
5 | * @LastEditors: mulingyuer
6 | * @Description: 路由鉴权类型
7 | * @FilePath: \spirit-app-microservice-admin\src\router\router-auth\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { RouterAuthContext } from "./context";
11 |
12 | /** 策略模式接口 */
13 | export interface AuthStrategy {
14 | execute(context: RouterAuthContext): void;
15 | }
16 |
17 | /** 鉴权类型 */
18 | export type AuthType =
19 | | "public" // 公开路由
20 | | "guest" // 访客路由
21 | | "required"; // 需要登录路由
22 |
--------------------------------------------------------------------------------
/frontend/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 17:32:12
4 | * @LastEditTime: 2024-12-31 09:11:47
5 | * @LastEditors: mulingyuer
6 | * @Description: routes
7 | * @FilePath: \frontend\src\router\routes\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { RouteRecordRaw } from "vue-router";
11 | import AdminLayout from "@/layout/admin-layout/index.vue";
12 |
13 | /** 根路由 */
14 | export const rootRoute: RouteRecordRaw = {
15 | path: "/",
16 | name: "Root",
17 | component: AdminLayout,
18 | redirect: { path: "/lora/flux" }, // HACK: 一定要用path重定向,name会导致守卫不触发
19 | children: []
20 | };
21 |
22 | /** 不存在的路由 */
23 | export const notFoundRoute: RouteRecordRaw = {
24 | path: "/:pathMatch(.*)*",
25 | name: "NotFound",
26 | component: AdminLayout,
27 | children: [
28 | {
29 | path: "404",
30 | name: "NotFound404",
31 | component: () => import("@/views/error/404.vue"),
32 | meta: {
33 | title: "404",
34 | icon: "ri-calendar-close-line",
35 | auth: "public"
36 | }
37 | }
38 | ]
39 | };
40 |
--------------------------------------------------------------------------------
/frontend/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 11:43:53
4 | * @LastEditTime: 2024-12-18 09:15:01
5 | * @LastEditors: mulingyuer
6 | * @Description: 数据仓库
7 | * @FilePath: \frontend\src\stores\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { App } from "vue";
11 | import { createPinia } from "pinia";
12 | import { createPersistedState } from "pinia-plugin-persistedstate";
13 |
14 | export const store = createPinia();
15 | store.use(
16 | createPersistedState({
17 | key: (id) => `${import.meta.env.VITE_APP_LOCAL_KEY_PREFIX}${id}`
18 | })
19 | );
20 |
21 | export const piniaStore = {
22 | install(app: App) {
23 | app.use(store);
24 | }
25 | };
26 |
27 | export * from "./modules";
28 |
--------------------------------------------------------------------------------
/frontend/src/stores/modules/app/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-29 17:34:26
4 | * @LastEditTime: 2024-09-29 17:35:52
5 | * @LastEditors: mulingyuer
6 | * @Description: 应用配置类型
7 | * @FilePath: \spirit-app-microservice-admin\src\stores\modules\app\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 路由动画类型 */
12 | export type RouteAnimateType =
13 | | "fade"
14 | | "fade-slide"
15 | | "fade-bottom"
16 | | "fade-scale"
17 | | "zoom-fade"
18 | | "zoom-out";
19 |
--------------------------------------------------------------------------------
/frontend/src/stores/modules/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 11:43:31
4 | * @LastEditTime: 2025-03-27 09:38:56
5 | * @LastEditors: mulingyuer
6 | * @Description: Module的入口文件
7 | * @FilePath: \frontend\src\stores\modules\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | export * from "./user";
11 | export * from "./app";
12 | export * from "./settings";
13 | export * from "./training";
14 | export * from "./modal-manager";
15 |
--------------------------------------------------------------------------------
/frontend/src/stores/modules/modal-manager/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-01 17:15:31
4 | * @LastEditTime: 2025-04-03 09:28:10
5 | * @LastEditors: mulingyuer
6 | * @Description: 弹窗数据管理器的类型
7 | * @FilePath: \frontend\src\stores\modules\modal-manager\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 视频预览弹窗数据 */
12 | export interface VideoPreviewModal {
13 | /** 是否打开 */
14 | open: boolean;
15 | /** 标题 */
16 | title: string;
17 | /** 视频地址 */
18 | src: string;
19 | }
20 |
21 | /** lora任务日志弹窗 */
22 | export interface LoraTaskLogModal {
23 | /** 是否打开 */
24 | open: boolean;
25 | /** 任务id */
26 | taskId: string;
27 | }
28 |
29 | /** 查看采样数据抽屉 */
30 | export interface ViewSamplingDrawerModal {
31 | /** 是否打开 */
32 | open: boolean;
33 | /** 采样数据路径 */
34 | filePath: string;
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/stores/modules/settings/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 16:17:03
4 | * @LastEditTime: 2025-03-28 11:53:47
5 | * @LastEditors: mulingyuer
6 | * @Description: 设置数据仓库的类型
7 | * @FilePath: \frontend\src\stores\modules\settings\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 训练器设置 */
12 | export interface TrainerSettings {
13 | /** 是否开启动态站点图标 */
14 | openAnimatedFavicon: boolean;
15 | /** 是否开启训练工具栏进度条背景 */
16 | openFooterBarProgress: boolean;
17 | /** 是否开启恢复训练中的任务表单数据 */
18 | enableTrainingTaskDataRecovery: boolean;
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/stores/modules/user/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-25 11:34:06
4 | * @LastEditTime: 2025-03-27 16:23:41
5 | * @LastEditors: mulingyuer
6 | * @Description: 用户数据模块
7 | * @FilePath: \frontend\src\stores\modules\user\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { defineStore } from "pinia";
11 |
12 | export const useUserStore = defineStore(
13 | "user",
14 | () => {
15 | const token = ref("");
16 | function setToken(newToken: string) {
17 | token.value = newToken;
18 | }
19 | function clearToken() {
20 | token.value = "";
21 | }
22 |
23 | /** 是否登录 */
24 | const isLogin = computed(() => Boolean(token.value));
25 |
26 | return {
27 | token,
28 | setToken,
29 | clearToken,
30 | isLogin
31 | };
32 | },
33 | {
34 | persist: true
35 | }
36 | );
37 |
38 | export type UseUserStore = ReturnType;
39 |
--------------------------------------------------------------------------------
/frontend/src/styles/element-plus/theme-dark.scss:
--------------------------------------------------------------------------------
1 | // only scss variables
2 | $--colors: (
3 | "primary": (
4 | "base": #20bda0
5 | ),
6 | "success": (
7 | "base": #35ba61
8 | ),
9 | "warning": (
10 | "base": #d3b32d
11 | ),
12 | "danger": (
13 | "base": #c14848
14 | ),
15 | "error": (
16 | "base": #d12626
17 | ),
18 | "info": (
19 | "base": #359fc4
20 | )
21 | );
22 |
23 | @use "element-plus/theme-chalk/src/dark/var.scss" with (
24 | $colors: $--colors
25 | );
26 |
--------------------------------------------------------------------------------
/frontend/src/styles/element-plus/theme-light.scss:
--------------------------------------------------------------------------------
1 | $--colors: (
2 | "primary": (
3 | "base": #20bda0
4 | ),
5 | "success": (
6 | "base": #2cd07e
7 | ),
8 | "warning": (
9 | "base": #f6c000
10 | ),
11 | "danger": (
12 | "base": #ff4136
13 | ),
14 | "error": (
15 | "base": #f8285a
16 | ),
17 | "info": (
18 | "base": #2cabe3
19 | )
20 | );
21 |
22 | @use "element-plus/theme-chalk/src/common/var.scss" with (
23 | $colors: $--colors
24 | );
25 |
--------------------------------------------------------------------------------
/frontend/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | // 统一样式入口
2 | @forward "element-plus/theme-chalk/src/dark/css-vars.scss";
3 | @forward "./element-plus/element-plus-reset.scss";
4 | @forward "./theme/light.scss";
5 | @forward "./theme/dark.scss";
6 | @forward "./transition.scss";
7 | @forward "./reset.scss";
8 |
--------------------------------------------------------------------------------
/frontend/src/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | // 全局混入
2 |
3 | // 限制一行
4 | @mixin text-ellipsis {
5 | white-space: nowrap;
6 | overflow: hidden;
7 | text-overflow: ellipsis;
8 | }
9 |
10 | // 限制多行
11 | @mixin ellipsis-rows($lines) {
12 | display: -webkit-box;
13 | -webkit-box-orient: vertical;
14 | -webkit-line-clamp: $lines;
15 | overflow: hidden;
16 | text-overflow: ellipsis;
17 | }
18 |
19 | // 禁止选中
20 | @mixin no-select {
21 | -webkit-touch-callout: none;
22 | -webkit-user-select: none;
23 | -khtml-user-select: none;
24 | -moz-user-select: none;
25 | -ms-user-select: none;
26 | user-select: none;
27 | }
28 |
29 | // 禁止拖拽
30 | @mixin no-drag {
31 | -webkit-user-drag: none;
32 | -khtml-user-drag: none;
33 | -moz-user-drag: none;
34 | -o-user-drag: none;
35 | user-select: none;
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/styles/nprogress.scss:
--------------------------------------------------------------------------------
1 | #nprogress .bar {
2 | background: #1b84ff;
3 | z-index: 9999;
4 | }
5 | #nprogress .peg {
6 | box-shadow:
7 | 0 0 10px #1b84ff,
8 | 0 0 5px #1b84ff;
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/styles/theme/dark.scss:
--------------------------------------------------------------------------------
1 | html.dark {
2 | --zl-admin-layout-bg: #0a0a0a;
3 | --zl-page-bg: #141414;
4 | --zl-aside-footer-box-shadow: rgba(255, 255, 255, 0.12);
5 | --zl-footer-bar-bg: #141414;
6 | --zl-switch-bg: #000;
7 | --zl-switch-color: #e5eaf3;
8 | --zl-switch-color-active: #141414;
9 | --zl-scrollbar: rgba(144, 147, 153, 0.4);
10 | --zl-two-split-gutter-bg: #141414;
11 | --zl-two-split-gutter-bg-hover: #1d1e1f;
12 | --zl-collapse-bg: #141414;
13 | --zl-field-set-wrapper-bg: #141414;
14 | --zl-split-right-panel-bg: #141414;
15 | --zl-toml-preview-bg: #141414;
16 | --zl-toml-preview-code-bg: transparent;
17 | --zl-ai-dataset-bg: #141414;
18 | --zl-context-menu-box-shadow: rgba(255, 255, 255, 0.12);
19 | --zl-context-menu-item-bg: #141414;
20 | --zl-ai-dataset-upload-mask: rgba(255, 255, 255, 0.12);
21 | --zl-ai-dataset-upload-bg: #141414;
22 | --zl-dashboard-bg: #141414;
23 | --zl-about-page-bg: #141414;
24 | --zl-about-page-color: #fff;
25 | --zl-popover-form-item-color: #fff;
26 | --zl-popover-form-item-tips-color: #2cabe3;
27 | --zl-task-item-color: #fff;
28 | --zl-website-banner-bg: #141414;
29 | --zl-website-banner-color: #fff;
30 | --zl-stop-training-dialog-color: #fff;
31 | --zl-frame-extraction-explain-bg: #0f0f0f;
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/styles/theme/light.scss:
--------------------------------------------------------------------------------
1 | html {
2 | --zl-admin-layout-bg: #f2f3f8;
3 | --zl-page-bg: #fff;
4 | --zl-aside-footer-box-shadow: rgba(0, 0, 0, 0.12);
5 | --zl-footer-bar-bg: #fff;
6 | --zl-switch-bg: #f0f0f0;
7 | --zl-switch-color: #606266;
8 | --zl-switch-color-active: #fff;
9 | --zl-scrollbar: rgba(144, 147, 153, 0.4);
10 | --zl-two-split-gutter-bg: #f2f3f8;
11 | --zl-two-split-gutter-bg-hover: #fff;
12 | --zl-collapse-bg: #fff;
13 | --zl-field-set-wrapper-bg: #fff;
14 | --zl-split-right-panel-bg: #fff;
15 | --zl-toml-preview-bg: #fff;
16 | --zl-toml-preview-code-bg: transparent;
17 | --zl-ai-dataset-bg: #fff;
18 | --zl-context-menu-box-shadow: rgba(0, 0, 0, 0.2);
19 | --zl-context-menu-item-bg: #fff;
20 | --zl-ai-dataset-upload-mask: rgba(0, 0, 0, 0.3);
21 | --zl-ai-dataset-upload-bg: #fff;
22 | --zl-dashboard-bg: #fff;
23 | --zl-about-page-bg: #fff;
24 | --zl-about-page-color: #000;
25 | --zl-popover-form-item-color: #000;
26 | --zl-popover-form-item-tips-color: #888;
27 | --zl-task-item-color: #000;
28 | --zl-website-banner-bg: #f7f9fd;
29 | --zl-website-banner-color: #141414;
30 | --zl-stop-training-dialog-color: #2f2e2e;
31 | --zl-frame-extraction-explain-bg: #f0f0f0;
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // 全局变量
2 | $zl-padding: 12px; // 内边距
3 | $zl-border-radius: 8px; // 圆角
4 | $zl-collapse-margin: 10px; // 折叠面板外边距
5 |
6 | $zl-aside-width: 210px; // 侧边栏宽度
7 | $zl-aside-mini-width: 65px; // 侧边栏最小宽度
8 | $zl-aside-mobile-width: 0px; // 侧边栏移动端宽度
9 | $zl-header-height: 56px; // 顶部高度
10 | $zl-footer-bar-height: 60px; // 底部栏高度
11 | $zl-scrollbar-width: 8px; // 双滚动条宽度
12 | $zl-view-footer-bar-height: calc(100vh - $zl-padding * 2 - $zl-footer-bar-height); // 内容高度
13 |
14 | $zl-ai-dataset-file-width: 90px; // AI数据集文件宽度
15 | $zl-ai-dataset-file-height: 82px; // AI数据集文件高度
16 |
--------------------------------------------------------------------------------
/frontend/src/utils/dayjs.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-10-11 17:10:47
4 | * @LastEditTime: 2025-02-24 16:03:07
5 | * @LastEditors: mulingyuer
6 | * @Description: dayjs封装
7 | * @FilePath: \frontend\src\utils\dayjs.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import dayjs from "dayjs";
11 | import "dayjs/locale/zh-cn";
12 | dayjs.locale("zh-cn");
13 |
14 | /** 格式化日期 */
15 | export function formatDate(date: Date | string | number, format: string): string {
16 | return dayjs(date).format(format);
17 | }
18 |
19 | export default dayjs;
20 |
--------------------------------------------------------------------------------
/frontend/src/utils/env.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-07 14:38:08
4 | * @LastEditTime: 2025-04-09 11:55:10
5 | * @LastEditors: mulingyuer
6 | * @Description: 环境变量
7 | * @FilePath: \frontend\src\utils\env.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | function withDefaults(env: T, defaults: Partial): T {
12 | return {
13 | ...defaults,
14 | ...env
15 | };
16 | }
17 |
18 | /** 获取环境变量 */
19 | export function getEnv(): ImportMetaEnv {
20 | return withDefaults(import.meta.env, {
21 | VITE_APP_WHITE_CHECK: "false",
22 | VITE_APP_LORA_OUTPUT_PARENT_PATH: "/root",
23 | VITE_APP_WAN_VIDEO_MAX_FRAMES: "129"
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/utils/event-bus/events.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-23 17:08:46
4 | * @LastEditTime: 2024-12-24 16:38:25
5 | * @LastEditors: mulingyuer
6 | * @Description: 事件类型
7 | * @FilePath: \frontend\src\utils\event-bus\events.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | // export enum EventType {
12 | // /** 开始监听LoRA训练 */
13 | // LORA_TRAIN_START = "lora_train_start",
14 | // /** 停止监听LoRA训练 */
15 | // LORA_TRAIN_STOP = "lora_train_stop",
16 | // /** LoRA训练完成 */
17 | // LORA_TRAIN_COMPLETE = "lora_train_complete",
18 | // /** LoRA训练异常 */
19 | // LORA_TRAIN_ERROR = "lora_train_error"
20 | // }
21 |
22 | export type Events = {
23 | /** 开始监听GPU */
24 | gpu_monitor_start: void;
25 | /** 停止监听GPU */
26 | gpu_monitor_stop: void;
27 | /** 开始监听LoRA训练 */
28 | lora_monitor_train_start: void;
29 | /** 停止监听LoRA训练 */
30 | lora_monitor_train_stop: void;
31 | /** LoRA训练完成 */
32 | lora_train_complete: void;
33 | /** LoRA训练异常 */
34 | lora_train_failed: void;
35 | /** 开始监听打标 */
36 | tag_monitor_start: void;
37 | /** 停止监听打标 */
38 | tag_monitor_stop: void;
39 | /** 打标完成 */
40 | tag_complete: void;
41 | /** 打标异常 */
42 | tag_failed: void;
43 | };
44 |
--------------------------------------------------------------------------------
/frontend/src/utils/event-bus/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-23 17:05:53
4 | * @LastEditTime: 2024-12-23 17:29:46
5 | * @LastEditors: mulingyuer
6 | * @Description: 事件总线
7 | * @FilePath: \frontend\src\utils\event-bus\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import mitt from "mitt";
11 | import type { Events } from "./events";
12 | export type { Events };
13 |
14 | const mittInstance = mitt();
15 |
16 | export { mittInstance as EventBus };
17 |
--------------------------------------------------------------------------------
/frontend/src/utils/file-manager/enums.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-02 11:27:54
4 | * @LastEditTime: 2025-04-02 11:27:54
5 | * @LastEditors: mulingyuer
6 | * @Description: 文件管理器枚举
7 | * @FilePath: \frontend\src\utils\file-manager\enums.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | export enum FileType {
11 | IMAGE = "image",
12 | TEXT = "text",
13 | VIDEO = "video"
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/utils/file-manager/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-02 11:26:07
4 | * @LastEditTime: 2025-04-02 11:28:15
5 | * @LastEditors: mulingyuer
6 | * @Description: 文件管理器的类型定义
7 | * @FilePath: \frontend\src\utils\file-manager\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { DirectoryFilesResult } from "@/api/common";
11 | import type { FileType } from "./enums";
12 |
13 | /** 基础数据 */
14 | export interface BaseFileItem {
15 | name: string;
16 | /** 路径 */
17 | path: string;
18 | /**
19 | * 图片的value是src
20 | * video的value是src
21 | * text的value是它的内容 */
22 | value: string;
23 | /** 文件mime */
24 | mime: string;
25 | /** 源数据 */
26 | raw: DirectoryFilesResult[number];
27 | }
28 |
29 | /** 图片数据 */
30 | export interface ImageFileItem extends BaseFileItem {
31 | type: FileType.IMAGE;
32 | /** 是否存在打标文件 */
33 | hasTagText: boolean;
34 | }
35 |
36 | /** text数据 */
37 | export interface TextFileItem extends BaseFileItem {
38 | type: FileType.TEXT;
39 | }
40 |
41 | /** 视频数据 */
42 | export interface VideoFileItem extends BaseFileItem {
43 | type: FileType.VIDEO;
44 | /** 是否存在打标文件 */
45 | hasTagText: boolean;
46 | }
47 |
48 | export type FileItem = ImageFileItem | TextFileItem | VideoFileItem;
49 |
50 | export type FileList = Array;
51 |
52 | /** 文件组件基础props */
53 | export interface BaseFileItemProps {
54 | /** 是否选中 */
55 | selected: boolean;
56 | }
57 |
--------------------------------------------------------------------------------
/frontend/src/utils/log.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-07 15:30:07
4 | * @LastEditTime: 2025-03-07 15:56:48
5 | * @LastEditors: mulingyuer
6 | * @Description: 日志
7 | * @FilePath: \frontend\src\utils\log.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import localforage from "localforage";
11 | export { serializeError } from "serialize-error";
12 |
13 | // 配置 localforage 只使用 localStorage
14 | const logStorage = localforage.createInstance({
15 | driver: localforage.LOCALSTORAGE, // 指定使用 localStorage
16 | name: "spirit-lora-trainer", // 可选:数据库名称
17 | storeName: "logs" // 可选:存储名称
18 | });
19 |
20 | export class Log {
21 | public static log(key: string, value: any) {
22 | return logStorage.setItem(key, value);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/utils/lora.helper/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-28 14:29:19
4 | * @LastEditTime: 2025-03-28 14:39:08
5 | * @LastEditors: mulingyuer
6 | * @Description: lora helper 类型声明
7 | * @FilePath: \frontend\src\utils\lora.helper\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | import type { BaseMonitorData } from "@/stores";
12 |
13 | /** 获取当前训练的任务数据结果 */
14 | export interface RunLoraTaskResult {
15 | type: TaskType;
16 | taskName: string;
17 | taskData: BaseMonitorData;
18 | }
19 |
20 | /** 恢复表单数据参数 */
21 | export interface RecoveryTaskFormDataOptions {
22 | /** 是否开启恢复训练中的任务表单数据 */
23 | enableTrainingTaskDataRecovery: boolean;
24 | /** 是否监听 */
25 | isListen: boolean;
26 | /** 任务id */
27 | taskId: string;
28 | /** 表单数据对象 */
29 | formData: Record;
30 | /** 是否显示恢复成功提示 */
31 | showRecoverySuccessTip?: boolean;
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/utils/monitor/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-08 15:50:32
4 | * @LastEditTime: 2025-04-10 17:16:32
5 | * @LastEditors: mulingyuer
6 | * @Description: 任务监控
7 | * @FilePath: \frontend\src\utils\monitor\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | export type * from "./types";
11 | export * from "./core/base-monitor";
12 | export * from "./core/lora-monitor";
13 |
--------------------------------------------------------------------------------
/frontend/src/utils/monitor/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-04-08 15:50:42
4 | * @LastEditTime: 2025-04-10 17:14:26
5 | * @LastEditors: mulingyuer
6 | * @Description: 任务监控器的类型定义
7 | * @FilePath: \frontend\src\utils\monitor\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 任务状态 */
12 | export type TaskStatus =
13 | | "idle" // 空闲
14 | | "querying" // 查询中
15 | | "paused" // 暂停
16 | | "success" // 成功
17 | | "failure"; // 失败
18 |
--------------------------------------------------------------------------------
/frontend/src/utils/nprogress.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 09:28:34
4 | * @LastEditTime: 2024-09-27 09:33:42
5 | * @LastEditors: mulingyuer
6 | * @Description: 进度条
7 | * @FilePath: \spirit-app-microservice-admin\src\utils\nprogress.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import NProgress from "nprogress";
11 | import "nprogress/nprogress.css";
12 | import "@/styles/nprogress.scss";
13 |
14 | //全局进度条的配置
15 | NProgress.configure({
16 | easing: "ease", // 动画方式
17 | speed: 500, // 递增进度条的速度
18 | showSpinner: false, // 是否显示加载ico
19 | trickleSpeed: 200, // 自动递增间隔
20 | minimum: 0.3 // 初始化时的最小百分比
21 | });
22 |
23 | export { NProgress };
24 |
--------------------------------------------------------------------------------
/frontend/src/utils/toml/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-12 16:17:12
4 | * @LastEditTime: 2025-02-18 08:54:25
5 | * @LastEditors: mulingyuer
6 | * @Description: toml相关工具
7 | * @FilePath: \frontend\src\utils\toml\index.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { stringify, parse } from "smol-toml";
11 | import { formatDate } from "@/utils/dayjs";
12 | import { downloadFile } from "@/utils/tools";
13 | import type { DownloadTomlFileOptions } from "./types";
14 |
15 | /** 生成toml字符串 */
16 | export function tomlStringify(obj: any): string {
17 | return stringify(obj);
18 | }
19 |
20 | /** toml字符串转对象 */
21 | export function tomlParse(text: string): T {
22 | return parse(text) as T;
23 | }
24 |
25 | /** 获取toml的file文件的文本内容 */
26 | export function readTomlFile(file: File): Promise {
27 | return new Promise((resolve, reject) => {
28 | const reader = new FileReader();
29 |
30 | reader.onload = function (e) {
31 | resolve(e.target!.result as T);
32 | };
33 |
34 | reader.onerror = function (e) {
35 | reject(new Error("读取toml文件失败: " + e.target!.error));
36 | };
37 |
38 | reader.readAsText(file);
39 | });
40 | }
41 |
42 | /** 将toml字符串转成文件并下载 */
43 | export function downloadTomlFile(options: DownloadTomlFileOptions) {
44 | const { text, fileNamePrefix } = options;
45 | let { fileName } = options;
46 | fileName = fileName ?? `${formatDate(new Date(), "YYYY-MM-DD HH-mm-ss")}.toml`;
47 | if (fileNamePrefix) fileName = `${fileNamePrefix}_${fileName}`;
48 |
49 | const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
50 | const url = URL.createObjectURL(blob);
51 |
52 | downloadFile(url, fileName);
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/utils/toml/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-01-07 17:30:56
4 | * @LastEditTime: 2025-01-07 17:32:12
5 | * @LastEditors: mulingyuer
6 | * @Description: toml工具类型
7 | * @FilePath: \frontend\src\utils\toml\types.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** 将toml字符串转成文件并下载参数 */
12 | export interface DownloadTomlFileOptions {
13 | /** toml字符串 */
14 | text: string;
15 | /** 文件名,默认为当前时间 */
16 | fileName?: string;
17 | /** 文件名前缀 */
18 | fileNamePrefix?: string;
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/views/error/404.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |

14 |
15 | 返回首页
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
41 |
--------------------------------------------------------------------------------
/frontend/src/views/iframe/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
18 |
38 |
39 |
56 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/AdvancedSettings/DataEnhancerOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/AdvancedSettings/DistributedTrainingOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/AdvancedSettings/LoggingOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/AdvancedSettings/TrainingPreviewOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/AdvancedSettings/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/flux/components/TrainingSamples/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
22 |
23 |
24 |
30 |
31 |
38 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/AdapterSettings.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
29 |
34 |
38 |
39 |
40 |
41 |
42 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/EvalSettings.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
25 |
26 |
31 |
36 |
37 |
42 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/AdapterTypeSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/ModelDtypeSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/ModelTimestepSampleMethodSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/ModelTransformerDtypeSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/OptimizerTypeSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/PartitionMethodSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/components/Form/VideoClipModeSelector.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/hunyuan-video/dataset.toml:
--------------------------------------------------------------------------------
1 | # 用于训练的分辨率,给定为方形图像的边长。您可以在此处设置多个尺寸。 24g显存最大只能跑512*512
2 | resolutions = [512]
3 | # 您也可以以 (宽度, 高度) 的形式给出分辨率。这并没有任何不同,只是
4 | # 另一种指定您想要训练的面积(即像素总数)的方式。
5 | # resolutions = [[1280, 720]]
6 |
7 | # 启用长宽比分桶。对于不同的长宽比桶,最终的尺寸将是
8 | # 面积匹配您上面配置的分辨率。
9 | enable_ar_bucket = true
10 |
11 | # 每个“[[directory]]”条目也可以指定长宽比和帧桶设置。
12 | # 目录级别的设置将覆盖顶级设置。
13 |
14 | # 最小和最大长宽比,以宽度/高度的比例给出。
15 | min_ar = 0.5
16 | max_ar = 2.0
17 | # 长宽比桶的总数,在 min_ar 和 max_ar 之间均匀分布(在对数空间中)。
18 | num_ar_buckets = 7
19 |
20 | # 可以手动指定 ar_buckets 而不是使用上述范围样式配置。
21 | # 每个条目可以是宽度/高度比,也可以是 (宽度, 高度) 对。但由于 TOML 的特性,您不能混合使用。
22 | # ar_buckets = [[512, 512], [448, 576]]
23 | # ar_buckets = [1.0, 1.5]
24 |
25 | # 高级的,输入框输入:1,2,3
26 | # 对于视频训练,您需要配置帧桶(类似于长宽比桶)。对于图像,总是会有一个帧桶为 1。视频将被分配到第一个长度
27 | # 大于或等于的帧桶。但是,视频永远不会被分配到图像帧桶(1);如果视频非常短,它将被丢弃。
28 | frame_buckets = [1, 33, 65]
29 |
30 | [[directory]]
31 | # 图像/视频目录及其对应的字幕文件路径。字幕文件应与媒体文件名匹配,但扩展名为 .txt。
32 | # 缺少字幕文件将记录一个警告,但将使用空字幕进行训练。
33 | path = '/home/anon/data/images/grayscale'
34 | # 数据集将像被重复这么多次一样进行操作。
35 | num_repeats = 10
36 | # 示例覆盖某些设置,并使用 ar_buckets 直接指定长宽比。
37 | # ar_buckets = [[448, 576]]
38 | # resolutions = [[448, 576]]
39 | # frame_buckets = [1]
40 |
41 | # 您可以列出多个目录。
42 |
43 | # [[directory]]
44 | # path = '/home/anon/data/images/something_else'
45 | # num_repeats = 5
46 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/AdvancedOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/DataEnhancerOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/DistributedTrainingOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/LoggingOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/NoiseOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/TrainingOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
30 |
31 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/TrainingPreviewOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/AdvancedSettings/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/sdxl/components/ModelParameters/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
26 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/wan-video/components/AdvancedSettings/TrainingDistributedOptions.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
25 |
26 |
31 |
37 |
38 |
39 |
40 |
41 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/wan-video/components/AdvancedSettings/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/frontend/src/views/lora/wan-video/components/WanDataSet/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/views/task/components/TaskEmpty.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/frontend/src/views/website/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
18 |
21 |
22 |
27 |
--------------------------------------------------------------------------------
/frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["src/**/*", "src, /**/*.vue", "types/**/*.d.ts", "src/request/axios.d.ts"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
8 | "lib": ["es2023", "dom"],
9 | "paths": {
10 | "@/*": ["./src/*"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node22/tsconfig.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "nightwatch.conf.*",
8 | "playwright.config.*",
9 | "types/env.d.ts",
10 | "vite-plugins/**/*.ts"
11 | ],
12 | "compilerOptions": {
13 | "composite": true,
14 | "noEmit": true,
15 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
16 |
17 | "module": "ESNext",
18 | "moduleResolution": "Bundler",
19 | "types": ["node"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/types/admin-app.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-27 17:02:33
4 | * @LastEditTime: 2024-12-04 09:36:52
5 | * @LastEditors: mulingyuer
6 | * @Description: 应用全局类型定义
7 | * @FilePath: \spirit-lora-trainer\frontend\types\admin-app.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | declare namespace AdminApp {
12 | /** 菜单数据类型 */
13 | interface Menu {
14 | path: string;
15 | name: string;
16 | title: string;
17 | icon?: string;
18 | children?: Menu[];
19 | }
20 | /** navTab数据类型 */
21 | interface NavTabData {
22 | fullPath: string;
23 | name?: string;
24 | title?: string;
25 | icon?: string;
26 | /** 是否固定(不允许关闭) */
27 | affix?: boolean;
28 | }
29 | }
30 |
31 | declare module "vuefinder/dist/vuefinder";
32 | declare module "vuefinder";
33 |
--------------------------------------------------------------------------------
/frontend/types/directives.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-04 17:10:51
4 | * @LastEditTime: 2024-12-04 17:16:10
5 | * @LastEditors: mulingyuer
6 | * @Description: 自定义指令类型声明
7 | * @FilePath: \frontend\types\directives.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { Component, Directive } from "vue";
11 | import type { ComplexityEnum } from "@/enums/complexity.enum";
12 |
13 | interface Directives {
14 | vComplexity: Directive;
15 | }
16 |
17 | declare module "vue" {
18 | // 在这里直接继承 Directives 即可
19 | interface ComponentCustomProperties extends Component, Directives {}
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/types/element-plus.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-26 09:08:40
4 | * @LastEditTime: 2025-03-26 09:28:08
5 | * @LastEditors: mulingyuer
6 | * @Description: 补充element-plus的类型定义文件
7 | * @FilePath: \frontend\types\element-plus.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { ElOption } from "element-plus";
11 |
12 | declare global {
13 | /** ElOption数据类型 */
14 | type ElOptionProps = InstanceType["$props"];
15 | type ElOptions = Array;
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/types/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | /** 本地环境 */
5 | NODE_ENV: string;
6 | /** 标题 */
7 | VITE_APP_TITLE: string;
8 | /** baseURL */
9 | VITE_APP_BASE_URL: string;
10 | /** 本地持久化key前缀 */
11 | VITE_APP_LOCAL_KEY_PREFIX: string;
12 | /** api请求地址 */
13 | VITE_APP_API_BASE_URL: string;
14 | /** 开启小白校验 */
15 | VITE_APP_WHITE_CHECK: string;
16 | /** lora输出路径前缀要求 */
17 | VITE_APP_LORA_OUTPUT_PARENT_PATH: string;
18 | /** wan视频训练max_frames */
19 | VITE_APP_WAN_VIDEO_MAX_FRAMES: string;
20 | }
21 |
22 | interface ImportMeta {
23 | readonly env: ImportMetaEnv;
24 | }
25 |
26 | /** git最后一次提交时间 */
27 | declare const __GIT_COMMIT_TIME__: string;
28 | /** git最后一次commit id */
29 | declare const __GIT_COMMIT_ID__: string;
30 |
--------------------------------------------------------------------------------
/frontend/types/router.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-09-26 14:55:57
4 | * @LastEditTime: 2025-03-28 10:17:02
5 | * @LastEditors: mulingyuer
6 | * @Description: 路由类型
7 | * @FilePath: \frontend\types\router.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import type { AuthType } from "@/router/router-auth";
11 |
12 | declare module "vue-router" {
13 | /** 路由配置项 */
14 | interface RouteMeta {
15 | /** 页面标题 */
16 | title?: string;
17 | /** 页面图标 */
18 | icon?: string;
19 | /** 是否隐藏菜单 */
20 | isHide?: boolean;
21 | /** 菜单排序 */
22 | sort?: number;
23 | /** 是否开启keepAlive */
24 | keepAlive?: boolean;
25 | /** 是否是访客页面 */
26 | auth?: AuthType;
27 | /** 是否固定(不允许关闭) */
28 | affix?: boolean;
29 | /** 是否显示footer-bar */
30 | showFooter?: boolean;
31 | /** 外链地址 */
32 | iframeLink?: string;
33 | /** 当前页面的任务类型 */
34 | loRATaskType?: TaskType;
35 | }
36 | }
37 |
38 | export {};
39 |
--------------------------------------------------------------------------------
/frontend/types/task.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-27 16:12:05
4 | * @LastEditTime: 2025-04-11 11:26:32
5 | * @LastEditors: mulingyuer
6 | * @Description: 全局lora类型定义
7 | * @FilePath: \frontend\types\task.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | /** lora训练任务 */
12 | type TaskType = "tag" | "flux" | "hunyuan-video" | "wan-video" | "none";
13 |
--------------------------------------------------------------------------------
/frontend/types/type-utils.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2025-03-25 15:56:35
4 | * @LastEditTime: 2025-03-25 17:02:07
5 | * @LastEditors: mulingyuer
6 | * @Description: 类型工具
7 | * @FilePath: \frontend\types\type-utils.d.ts
8 | * 怎么可能会有bug!!!
9 | */
10 |
11 | // 提取必填属性的键
12 | type RequiredKeys = {
13 | [K in keyof T]-?: undefined extends T[K] ? never : K;
14 | }[keyof T];
15 |
16 | // 挑选必填属性构造新类型
17 | type RequiredProps = Pick>;
18 |
19 | // 将类型转换为可读性更好的类型
20 | type Prettify = {
21 | [K in keyof T]: T[K];
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/vite-plugins/git-commit-info.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: mulingyuer
3 | * @Date: 2024-12-26 10:15:13
4 | * @LastEditTime: 2024-12-31 11:38:50
5 | * @LastEditors: mulingyuer
6 | * @Description: 获取git最后一次提交时间
7 | * @FilePath: \frontend\vite-plugins\git-commit-info.ts
8 | * 怎么可能会有bug!!!
9 | */
10 | import { execSync } from "child_process";
11 |
12 | /** 获取git最后一次提交时间 */
13 | function getGitLastCommitTime(): string {
14 | const stdout = execSync("git log -1 --format=%cI").toString().trim();
15 | return new Date(stdout).toISOString();
16 | }
17 |
18 | /** 获取git最后一次commit id */
19 | function getGitLastCommitId(): string {
20 | return execSync("git log -1 --format=%h").toString().trim();
21 | }
22 |
23 | export function getCommitInfo() {
24 | try {
25 | return {
26 | lastTime: getGitLastCommitTime(),
27 | lastId: getGitLastCommitId()
28 | };
29 | } catch (error) {
30 | throw new Error(`获取git最后一次提交时间失败: ${error}`);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastone-spirit/spirit-lora-trainer/e9d638b624003d2c2148a7ec94b81faf733b08b9/public/images/logo.png
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | minversion = 7.0
3 | addopts =
4 | -vvv
5 | --cov=.
6 | pythonpath =backend/
7 | testpaths =
8 | tests
9 | log_cli = true
10 | log_cli_level = INFO
11 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
12 | log_cli_date_format = %Y-%m-%d %H:%M:%S
--------------------------------------------------------------------------------