feat: 添加 LoRA 类型和反馈控制器,优化数据集名称和目标模型保存功能

This commit is contained in:
Vixalie
2026-03-28 17:53:09 +08:00
parent 7eaafb068d
commit 3a7b21e3e7
4 changed files with 131 additions and 32 deletions
+11
View File
@@ -12,3 +12,14 @@ export const ModelChoices: Option<TargetModels>[] = [
{ label: 'Flux', value: 'flux' },
{ label: 'Flux 2', value: 'flux2' },
]
export type LoRATypes = 'char' | 'clothing' | 'style' | 'concept' | 'scene' | 'other';
export const LoRATypeChoices: Option<LoRATypes>[] = [
{ label: 'Character', value: 'char' },
{ label: 'Clothing', value: 'clothing' },
{ label: 'Style', value: 'style' },
{ label: 'Concept', value: 'concept' },
{ label: 'Scene', value: 'scene' },
{ label: 'Other', value: 'other' },
]
+55
View File
@@ -0,0 +1,55 @@
export type SaveFeedbackState = 'idle' | 'updated' | 'not-updated';
export function createSaveFeedbackController(
setState: (state: SaveFeedbackState) => void,
resetDelayMs = 2000,
) {
let feedbackTimer: ReturnType<typeof setTimeout> | null = null;
const resetLater = () => {
if (feedbackTimer) {
clearTimeout(feedbackTimer);
}
feedbackTimer = setTimeout(() => {
setState('idle');
}, resetDelayMs);
};
return {
markUpdated() {
setState('updated');
resetLater();
},
markNotUpdated() {
setState('not-updated');
resetLater();
},
dispose() {
if (feedbackTimer) {
clearTimeout(feedbackTimer);
}
},
};
}
export function createDebouncedTrigger(callback: () => void, delayMs: number) {
let timer: ReturnType<typeof setTimeout> | null = null;
return {
trigger() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
callback();
}, delayMs);
},
cancel() {
if (timer) {
clearTimeout(timer);
}
},
};
}
@@ -1,16 +1,65 @@
<script lang="ts">
import { activeDatasetMeta } from '$lib/stores/dataset';
import { onMount } from 'svelte';
import { activeDatasetMeta, updateActiveDatasetMeta } from '$lib/stores/dataset';
import {
createDebouncedTrigger,
createSaveFeedbackController,
type SaveFeedbackState,
} from '$lib/utils/form-save';
import { onDestroy, onMount } from 'svelte';
import { get } from 'svelte/store';
let name = $state('');
let saveFeedback = $state<SaveFeedbackState>('idle');
const feedback = createSaveFeedbackController((state) => {
saveFeedback = state;
});
onMount(() => {
name = get(activeDatasetMeta)?.name ?? '';
});
onDestroy(() => {
saveLater.cancel();
feedback.dispose();
});
async function saveDatasetName(value: string) {
const nextName = value.trim();
if (!nextName) {
feedback.markNotUpdated();
return;
}
try {
await updateActiveDatasetMeta({ name: nextName });
feedback.markUpdated();
} catch (error) {
console.error('Failed to save dataset name:', error);
feedback.markNotUpdated();
}
}
const saveLater = createDebouncedTrigger(() => {
void saveDatasetName(name);
}, 5000);
function saveNameDelayed() {
saveLater.trigger();
}
</script>
<label class="input w-fit in-focus-within:outline-0">
<label
class={[
'input w-fit in-focus-within:outline-0 transition-colors duration-300 ease-out',
saveFeedback === 'updated' && 'border-green-500',
saveFeedback === 'not-updated' && 'border-red-500',
]}>
<span class="label min-w-[10em]">Dataset Name</span>
<input type="text" bind:value={name} class="min-w-[20em] focus:outline-0" />
<input
type="text"
bind:value={name}
oninput={saveNameDelayed}
class="min-w-[20em] focus:outline-0" />
</label>
+12 -28
View File
@@ -1,62 +1,46 @@
<script lang="ts">
import { activeDatasetMeta, updateActiveDatasetMeta } from '$lib/stores/dataset';
import { ModelChoices, type TargetModels } from '$lib/types/models';
import { createSaveFeedbackController, type SaveFeedbackState } from '$lib/utils/form-save';
import { onDestroy, onMount } from 'svelte';
import { get } from 'svelte/store';
let selected: TargetModels | null = $state(null);
let updated = $state(false);
let notUpdated = $state(false);
let feedbackTimer: ReturnType<typeof setTimeout> | null = null;
let saveFeedback = $state<SaveFeedbackState>('idle');
const feedback = createSaveFeedbackController((state) => {
saveFeedback = state;
});
onMount(() => {
selected = get(activeDatasetMeta)?.targetModel ?? null;
});
onDestroy(() => {
if (feedbackTimer) {
clearTimeout(feedbackTimer);
}
feedback.dispose();
});
function resetFeedbackIn2s() {
if (feedbackTimer) {
clearTimeout(feedbackTimer);
}
feedbackTimer = setTimeout(() => {
updated = false;
notUpdated = false;
}, 2000);
}
async function saveTargetModel() {
if (!selected) {
updated = false;
notUpdated = true;
resetFeedbackIn2s();
feedback.markNotUpdated();
return;
}
try {
await updateActiveDatasetMeta({ targetModel: selected });
updated = true;
notUpdated = false;
feedback.markUpdated();
} catch (error) {
console.error('Failed to save target model:', error);
updated = false;
notUpdated = true;
feedback.markNotUpdated();
}
resetFeedbackIn2s();
}
</script>
<label
class={[
'input w-fit in-focus-within:outline-0 transition-colors duration-300 ease-out',
updated && 'border-green-500',
notUpdated && 'border-red-500',
saveFeedback === 'updated' && 'border-green-500',
saveFeedback === 'not-updated' && 'border-red-500',
]}>
<span class="label min-w-[10em]">Target Model</span>
<select bind:value={selected} onchange={saveTargetModel} class="min-w-[20em] focus:outline-0">