refactor: 添加数据集元数据加载和保存功能,重构相关逻辑
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
use crate::config::{AppConfig, AppConfigState, ProxyKind};
|
use crate::config::{AppConfig, AppConfigState, ProxyKind};
|
||||||
|
use crate::dataset_meta::{load_dataset_meta, save_dataset_meta, DatasetMeta};
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
use tauri::{AppHandle, WebviewWindow};
|
use tauri::{AppHandle, WebviewWindow};
|
||||||
|
|
||||||
@@ -55,6 +56,16 @@ pub fn list_proxy_kind_options() -> Vec<SelectOption> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn load_dataset_meta_by_dir(dataset_dir: String) -> Result<DatasetMeta, String> {
|
||||||
|
load_dataset_meta(&dataset_dir).map_err(|e| format!("读取数据集元数据失败: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn save_dataset_meta_by_dir(dataset_dir: String, meta: DatasetMeta) -> Result<(), String> {
|
||||||
|
save_dataset_meta(&dataset_dir, &meta).map_err(|e| format!("保存数据集元数据失败: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
fn proxy_kind_value(kind: &ProxyKind) -> &'static str {
|
fn proxy_kind_value(kind: &ProxyKind) -> &'static str {
|
||||||
match kind {
|
match kind {
|
||||||
ProxyKind::Http => "Http",
|
ProxyKind::Http => "Http",
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
const DATASET_META_FILE: &str = "meta.toml";
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DatasetMeta {
|
||||||
|
pub name: String,
|
||||||
|
pub target_model: String,
|
||||||
|
pub lora_type: String,
|
||||||
|
pub unified_image_size: bool,
|
||||||
|
pub unified_image_ratio: bool,
|
||||||
|
pub image_size: [u32; 2],
|
||||||
|
pub trigger_words: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatasetMeta {
|
||||||
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
|
if self.name.trim().is_empty() {
|
||||||
|
anyhow::bail!("数据集名称不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.target_model.trim().is_empty() {
|
||||||
|
anyhow::bail!("目标模型不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.image_size[0] == 0 || self.image_size[1] == 0 {
|
||||||
|
anyhow::bail!("imageSize 必须为正整数")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dataset_meta_path(dataset_dir: &str) -> PathBuf {
|
||||||
|
PathBuf::from(dataset_dir).join(DATASET_META_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_dataset_meta(dataset_dir: &str, meta: &DatasetMeta) -> anyhow::Result<()> {
|
||||||
|
meta.validate()?;
|
||||||
|
|
||||||
|
let meta_path = dataset_meta_path(dataset_dir);
|
||||||
|
if let Some(parent_dir) = meta_path.parent() {
|
||||||
|
fs::create_dir_all(parent_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = toml::to_string_pretty(meta)?;
|
||||||
|
fs::write(meta_path, content)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_dataset_meta(dataset_dir: &str) -> anyhow::Result<DatasetMeta> {
|
||||||
|
let meta_path = dataset_meta_path(dataset_dir);
|
||||||
|
if !meta_path.exists() {
|
||||||
|
anyhow::bail!("缺少 meta.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs::read_to_string(meta_path)?;
|
||||||
|
let meta: DatasetMeta = toml::from_str(&content)?;
|
||||||
|
meta.validate()?;
|
||||||
|
Ok(meta)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod dataset_meta;
|
||||||
|
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
@@ -19,7 +20,9 @@ pub fn run() {
|
|||||||
commands::set_window_title,
|
commands::set_window_title,
|
||||||
commands::load_app_config,
|
commands::load_app_config,
|
||||||
commands::save_app_config,
|
commands::save_app_config,
|
||||||
commands::list_proxy_kind_options
|
commands::list_proxy_kind_options,
|
||||||
|
commands::load_dataset_meta_by_dir,
|
||||||
|
commands::save_dataset_meta_by_dir
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { DatasetMeta, ImageMeta } from "$lib/types/meta";
|
import type { DatasetMeta, ImageMeta } from "$lib/types/meta";
|
||||||
import { join } from "@tauri-apps/api/path";
|
import { saveDatasetMetaByDir } from "$lib/utils/dataset-meta";
|
||||||
import { writeTextFile } from "@tauri-apps/plugin-fs";
|
|
||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
|
|
||||||
export const openedDatasetDir = writable<string | null>(null);
|
export const openedDatasetDir = writable<string | null>(null);
|
||||||
@@ -25,8 +24,7 @@ export async function updateActiveDatasetMeta(updater: DatasetMetaUpdater): Prom
|
|||||||
const nextMeta =
|
const nextMeta =
|
||||||
typeof updater === "function" ? updater(currentMeta) : { ...currentMeta, ...updater };
|
typeof updater === "function" ? updater(currentMeta) : { ...currentMeta, ...updater };
|
||||||
|
|
||||||
const metaPath = await join(datasetDir, "meta.json");
|
await saveDatasetMetaByDir(datasetDir, nextMeta);
|
||||||
await writeTextFile(metaPath, JSON.stringify(nextMeta, null, 2));
|
|
||||||
activeDatasetMeta.set(nextMeta);
|
activeDatasetMeta.set(nextMeta);
|
||||||
|
|
||||||
return nextMeta;
|
return nextMeta;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type { DatasetMeta } from '$lib/types/meta';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
|
export async function loadDatasetMetaByDir(datasetDir: string) {
|
||||||
|
return invoke<DatasetMeta>('load_dataset_meta_by_dir', { datasetDir });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveDatasetMetaByDir(datasetDir: string, meta: DatasetMeta) {
|
||||||
|
await invoke('save_dataset_meta_by_dir', { datasetDir, meta });
|
||||||
|
}
|
||||||
@@ -4,15 +4,56 @@ import NewFolder from '$lib/components/icons/NewFolder.svelte';
|
|||||||
import OpenFolder from '$lib/components/icons/OpenFolder.svelte';
|
import OpenFolder from '$lib/components/icons/OpenFolder.svelte';
|
||||||
import { openedDatasetDir } from '$lib/stores/dataset';
|
import { openedDatasetDir } from '$lib/stores/dataset';
|
||||||
import { currentActivate } from '$lib/stores/navigate';
|
import { currentActivate } from '$lib/stores/navigate';
|
||||||
import { isDatasetMeta } from '$lib/types/meta';
|
import { loadDatasetMetaByDir } from '$lib/utils/dataset-meta';
|
||||||
import { join } from '@tauri-apps/api/path';
|
|
||||||
import { message, open } from '@tauri-apps/plugin-dialog';
|
import { message, open } from '@tauri-apps/plugin-dialog';
|
||||||
import { readDir, readTextFile } from '@tauri-apps/plugin-fs';
|
|
||||||
|
|
||||||
afterNavigate(() => {
|
afterNavigate(() => {
|
||||||
currentActivate.set('start');
|
currentActivate.set('start');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function normalizeErrorMessage(error: unknown): string {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDatasetLoadPrompt(error: unknown): { title: string; text: string } {
|
||||||
|
const message = normalizeErrorMessage(error);
|
||||||
|
const detail = message.replace('读取数据集元数据失败:', '').trim();
|
||||||
|
|
||||||
|
if (detail.includes('缺少 meta.toml')) {
|
||||||
|
return {
|
||||||
|
title: 'Missing meta.toml',
|
||||||
|
text: 'The selected folder does not contain meta.toml. Please choose a valid dataset folder.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail.includes('TOML parse error')) {
|
||||||
|
return {
|
||||||
|
title: 'Invalid meta.toml',
|
||||||
|
text: 'meta.toml has invalid TOML syntax. Please fix the file and try again.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail.includes('不能为空') || detail.includes('imageSize 必须为正整数')) {
|
||||||
|
return {
|
||||||
|
title: 'Invalid Dataset Metadata',
|
||||||
|
text: `meta.toml content is invalid: ${detail}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Invalid Dataset Folder',
|
||||||
|
text: `Failed to read dataset metadata: ${detail || 'Unknown error'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function loadDataset() {
|
async function loadDataset() {
|
||||||
try {
|
try {
|
||||||
const selectResult = await open({
|
const selectResult = await open({
|
||||||
@@ -34,25 +75,13 @@ async function loadDataset() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = await readDir(selectedPath);
|
try {
|
||||||
const hasMetaFile = entries.some((entry) => entry.name === 'meta.json');
|
await loadDatasetMetaByDir(selectedPath);
|
||||||
|
} catch (error) {
|
||||||
if (!hasMetaFile) {
|
const prompt = resolveDatasetLoadPrompt(error);
|
||||||
await message('The selected folder is not a valid dataset folder.', {
|
await message(prompt.text, {
|
||||||
kind: 'warning',
|
kind: 'warning',
|
||||||
title: 'Invalid Dataset Folder',
|
title: prompt.title,
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaPath = await join(selectedPath, 'meta.json');
|
|
||||||
const metaText = await readTextFile(metaPath);
|
|
||||||
const parsedMeta: unknown = JSON.parse(metaText);
|
|
||||||
|
|
||||||
if (!isDatasetMeta(parsedMeta)) {
|
|
||||||
await message('Dataset meta information is invalid.', {
|
|
||||||
kind: 'warning',
|
|
||||||
title: 'Invalid meta.json',
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { activeDatasetImageMetas, activeDatasetMeta, openedDatasetDir } from '$lib/stores/dataset';
|
import { activeDatasetImageMetas, activeDatasetMeta, openedDatasetDir } from '$lib/stores/dataset';
|
||||||
import { isDatasetMeta, type ImageMeta } from '$lib/types/meta';
|
import type { ImageMeta } from '$lib/types/meta';
|
||||||
|
import { loadDatasetMetaByDir } from '$lib/utils/dataset-meta';
|
||||||
import { join } from '@tauri-apps/api/path';
|
import { join } from '@tauri-apps/api/path';
|
||||||
import { readDir, readTextFile } from '@tauri-apps/plugin-fs';
|
import { readDir, readTextFile } from '@tauri-apps/plugin-fs';
|
||||||
import { isNil } from 'es-toolkit';
|
import { isNil } from 'es-toolkit';
|
||||||
@@ -16,18 +17,8 @@ export const load = (async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const metaPath = await join(activeDataset, 'meta.json');
|
const meta = await loadDatasetMetaByDir(activeDataset);
|
||||||
const metaText = await readTextFile(metaPath);
|
activeDatasetMeta.set(meta);
|
||||||
const parsedMeta: unknown = JSON.parse(metaText);
|
|
||||||
|
|
||||||
if (!isDatasetMeta(parsedMeta)) {
|
|
||||||
activeDatasetMeta.set(null);
|
|
||||||
activeDatasetImageMetas.set([]);
|
|
||||||
await goto('/boot');
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
activeDatasetMeta.set(parsedMeta);
|
|
||||||
|
|
||||||
const entries = await readDir(activeDataset);
|
const entries = await readDir(activeDataset);
|
||||||
const hasImagesMeta = entries.some((entry) => entry.name === 'images.json');
|
const hasImagesMeta = entries.some((entry) => entry.name === 'images.json');
|
||||||
|
|||||||
Reference in New Issue
Block a user