├── .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 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/assets/images/ai-dataset/text_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/src/assets/images/logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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 | 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 | 15 | 16 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/components/Dialog/LoraTaskLogDialog.vue: -------------------------------------------------------------------------------- 1 | 10 | 24 | 25 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/src/components/Dialog/NetworkRetryDialog.vue: -------------------------------------------------------------------------------- 1 | 10 | 30 | 31 | 45 | 46 | 56 | -------------------------------------------------------------------------------- /frontend/src/components/FieldSetWrapper/FieldSetWrapper.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | 24 | 31 | 32 | 56 | -------------------------------------------------------------------------------- /frontend/src/components/FileManager/FileItem/ImageFile.vue: -------------------------------------------------------------------------------- 1 | 10 | 29 | 30 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /frontend/src/components/FileManager/FileItem/TextFile.vue: -------------------------------------------------------------------------------- 1 | 10 | 16 | 17 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /frontend/src/components/FileManager/FileItem/VideoFile.vue: -------------------------------------------------------------------------------- 1 | 10 | 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 | 22 | 23 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagAddGlobalPromptSwitch.vue: -------------------------------------------------------------------------------- 1 | 10 | 20 | 21 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagAdvancedSwitch.vue: -------------------------------------------------------------------------------- 1 | 10 | 20 | 21 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagAppendSwitch.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | 16 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagDirectory.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | 16 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagGlobalPrompt.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | 16 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagJoyCaptionPrompt.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | 16 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagModelSelect.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagResetButton.vue: -------------------------------------------------------------------------------- 1 | 10 | 26 | 27 | 57 | 58 | 69 | -------------------------------------------------------------------------------- /frontend/src/components/Form/DataSet-v2/TagSubmitButton.vue: -------------------------------------------------------------------------------- 1 | 10 | 26 | 27 | 57 | 58 | 69 | -------------------------------------------------------------------------------- /frontend/src/components/Form/FileSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Form/FluxNetworkModuleSelect.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /frontend/src/components/Form/FolderSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Form/LrSchedulerSelect.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /frontend/src/components/Form/ModelSaveFormatSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/components/Form/ModelSavePrecisionSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/components/Form/PopoverFormItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 27 | 28 | 45 | 46 | 64 | -------------------------------------------------------------------------------- /frontend/src/components/Form/SDXLNetworkModuleSelect.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/Form/WeightingSchemeSelect.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /frontend/src/components/GlobalModalManager/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 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 | 20 | 21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/layout/admin-layout/components/Aside/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 27 | 28 | 46 | -------------------------------------------------------------------------------- /frontend/src/layout/admin-layout/components/Aside/Logo.vue: -------------------------------------------------------------------------------- 1 | 10 | 18 | 19 | 26 | 27 | 52 | -------------------------------------------------------------------------------- /frontend/src/layout/admin-layout/components/Aside/Menu.vue: -------------------------------------------------------------------------------- 1 | 10 | 26 | 27 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /frontend/src/layout/admin-layout/components/Aside/MenuItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | 24 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/layout/admin-layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 19 | 20 | 25 | 26 | 32 | -------------------------------------------------------------------------------- /frontend/src/layout/blank-layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 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 | 20 | 21 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 38 | 39 | 56 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/AdvancedSettings/DataEnhancerOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 31 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/AdvancedSettings/DistributedTrainingOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/AdvancedSettings/LoggingOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/AdvancedSettings/TrainingPreviewOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/AdvancedSettings/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | 24 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /frontend/src/views/lora/flux/components/TrainingSamples/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 39 | 40 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/AdapterSettings.vue: -------------------------------------------------------------------------------- 1 | 10 | 41 | 42 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/EvalSettings.vue: -------------------------------------------------------------------------------- 1 | 10 | 50 | 51 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/AdvancedSettings/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/AdapterTypeSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/ModelDtypeSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/ModelTimestepSampleMethodSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/ModelTransformerDtypeSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/OptimizerTypeSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/PartitionMethodSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 22 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/hunyuan-video/components/Form/VideoClipModeSelector.vue: -------------------------------------------------------------------------------- 1 | 10 | 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 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/DataEnhancerOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 31 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/DistributedTrainingOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/LoggingOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/NoiseOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 39 | 40 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/TrainingOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 47 | 48 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/TrainingPreviewOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/AdvancedSettings/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | 24 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /frontend/src/views/lora/sdxl/components/ModelParameters/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 41 | 42 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /frontend/src/views/lora/wan-video/components/AdvancedSettings/TrainingDistributedOptions.vue: -------------------------------------------------------------------------------- 1 | 10 | 40 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/lora/wan-video/components/AdvancedSettings/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/src/views/lora/wan-video/components/WanDataSet/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 20 | 21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/src/views/task/components/TaskEmpty.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | 16 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /frontend/src/views/website/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 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 --------------------------------------------------------------------------------