Compare commits
13 Commits
2c82f7cfc1
...
947ea31fe0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
947ea31fe0 | ||
|
|
e726dfce11 | ||
|
|
7655a8d441 | ||
|
|
2707e9ecdb | ||
|
|
eefbe42f2b | ||
|
|
4d58953c9b | ||
|
|
6c083dad83 | ||
|
|
cdf18d4397 | ||
|
|
edba1686cd | ||
|
|
cfdc05d9dd | ||
|
|
d5c1d2bdc2 | ||
|
|
109f77014b | ||
|
|
5d02728e1c |
@@ -24,4 +24,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
blake3 = { version = "1.8.3", features = ["serde", "digest", "pure"] }
|
||||
anyhow = "1.0.102"
|
||||
thiserror = "2.0.18"
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
use crate::config::{AppConfig, AppConfigState, ProxyKind};
|
||||
use tauri::State;
|
||||
use tauri::{AppHandle, WebviewWindow};
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SelectOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_window_title(
|
||||
app_handle: AppHandle,
|
||||
@@ -12,3 +21,43 @@ pub fn set_window_title(
|
||||
.unwrap_or(app_name);
|
||||
window.set_title(&new_title).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn load_app_config(state: State<'_, AppConfigState>) -> Result<AppConfig, String> {
|
||||
state.get().map_err(|e| format!("读取应用配置失败: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_app_config(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, AppConfigState>,
|
||||
config: AppConfig,
|
||||
) -> Result<(), String> {
|
||||
config
|
||||
.save(&app_handle)
|
||||
.map_err(|e| format!("保存应用配置失败: {e}"))?;
|
||||
state
|
||||
.set(config)
|
||||
.map_err(|e| format!("更新应用配置状态失败: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_proxy_kind_options() -> Vec<SelectOption> {
|
||||
vec![
|
||||
SelectOption {
|
||||
label: "HTTP".to_string(),
|
||||
value: proxy_kind_value(&ProxyKind::Http).to_string(),
|
||||
},
|
||||
SelectOption {
|
||||
label: "SOCKS5".to_string(),
|
||||
value: proxy_kind_value(&ProxyKind::Socks5).to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn proxy_kind_value(kind: &ProxyKind) -> &'static str {
|
||||
match kind {
|
||||
ProxyKind::Http => "Http",
|
||||
ProxyKind::Socks5 => "Socks5",
|
||||
}
|
||||
}
|
||||
|
||||
101
src-tauri/src/config.rs
Normal file
101
src-tauri/src/config.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, path::PathBuf, sync::RwLock};
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum ProxyKind {
|
||||
Http,
|
||||
Socks5,
|
||||
}
|
||||
|
||||
impl Default for ProxyKind {
|
||||
fn default() -> Self {
|
||||
Self::Http
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyConfig {
|
||||
pub enabled: bool,
|
||||
pub kind: ProxyKind,
|
||||
pub host: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppConfig {
|
||||
pub proxy: Option<ProxyConfig>,
|
||||
}
|
||||
|
||||
pub struct AppConfigState {
|
||||
inner: RwLock<AppConfig>,
|
||||
}
|
||||
|
||||
impl AppConfigState {
|
||||
pub fn new(config: AppConfig) -> Self {
|
||||
Self {
|
||||
inner: RwLock::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> anyhow::Result<AppConfig> {
|
||||
let guard = self
|
||||
.inner
|
||||
.read()
|
||||
.map_err(|_| anyhow::anyhow!("读取应用配置状态失败"))?;
|
||||
Ok(guard.clone())
|
||||
}
|
||||
|
||||
pub fn set(&self, config: AppConfig) -> anyhow::Result<()> {
|
||||
let mut guard = self
|
||||
.inner
|
||||
.write()
|
||||
.map_err(|_| anyhow::anyhow!("写入应用配置状态失败"))?;
|
||||
*guard = config;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
fn config_path(app_handle: &AppHandle) -> anyhow::Result<PathBuf> {
|
||||
Ok(app_handle
|
||||
.path()
|
||||
.resolve("config.json", BaseDirectory::AppData)?)
|
||||
}
|
||||
|
||||
pub fn save(&self, app_handle: &AppHandle) -> anyhow::Result<()> {
|
||||
let config_path = Self::config_path(app_handle)?;
|
||||
if let Some(parent_dir) = config_path.parent() {
|
||||
fs::create_dir_all(parent_dir)?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string_pretty(self)?;
|
||||
fs::write(config_path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(app_handle: &AppHandle) -> anyhow::Result<Self> {
|
||||
let config_path = Self::config_path(app_handle)?;
|
||||
if !config_path.exists() {
|
||||
return Ok(Self::default());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(config_path)?;
|
||||
Ok(serde_json::from_str(&content)?)
|
||||
}
|
||||
|
||||
pub fn load_or_init(app_handle: &AppHandle) -> anyhow::Result<Self> {
|
||||
let config_path = Self::config_path(app_handle)?;
|
||||
if !config_path.exists() {
|
||||
let default_config = Self::default();
|
||||
default_config.save(app_handle)?;
|
||||
return Ok(default_config);
|
||||
}
|
||||
|
||||
Self::load(app_handle)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
mod commands;
|
||||
mod config;
|
||||
|
||||
use tauri::Manager;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@@ -6,7 +9,18 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![commands::set_window_title])
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle().clone();
|
||||
let config = config::AppConfig::load_or_init(&app_handle)?;
|
||||
app.manage(config::AppConfigState::new(config));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::set_window_title,
|
||||
commands::load_app_config,
|
||||
commands::save_app_config,
|
||||
commands::list_proxy_kind_options
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
5
src/lib/components/icons/Check.svelte
Normal file
5
src/lib/components/icons/Check.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...$$props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m9.55 15.88l8.802-8.801q.146-.146.344-.156t.363.156t.166.357t-.165.356l-8.944 8.95q-.243.243-.566.243t-.566-.243l-4.05-4.05q-.146-.146-.152-.347t.158-.366t.357-.165t.357.165z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 299 B |
5
src/lib/components/icons/Exclamation.svelte
Normal file
5
src/lib/components/icons/Exclamation.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...$$props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.643 13.933q-.143-.143-.143-.356v-7q0-.212.144-.356t.357-.144t.356.144t.143.356v7q0 .213-.144.356t-.357.144t-.356-.144m0 3.846q-.143-.144-.143-.357t.144-.356t.357-.143t.356.144t.143.357t-.144.356t-.357.143t-.356-.144" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
5
src/lib/components/icons/TriangleRight.svelte
Normal file
5
src/lib/components/icons/TriangleRight.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...$$props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.91 15.242q-.168 0-.289-.11q-.121-.112-.121-.293V9.162q0-.182.124-.293t.288-.111q.042 0 .284.13l2.677 2.678q.093.092.143.199t.05.235t-.05.235t-.143.2l-2.677 2.677q-.055.055-.129.093q-.073.037-.157.037" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 328 B |
5
src/lib/components/icons/Warning.svelte
Normal file
5
src/lib/components/icons/Warning.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...$$props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4.126 20q-.234 0-.414-.111t-.28-.293q-.108-.179-.12-.387q-.01-.209.118-.421L11.3 5.212q.128-.212.308-.308T12 4.808t.391.096t.308.308l7.871 13.576q.128.212.115.417t-.118.391t-.282.295t-.41.109zm.324-1h15.1L12 6zm7.984-1.566q.182-.182.182-.434t-.182-.434t-.434-.181t-.434.181t-.182.434t.182.434t.434.181t.434-.181m-.077-2.193q.143-.144.143-.356v-4q0-.213-.144-.357t-.357-.143t-.356.143t-.143.357v4q0 .212.144.356t.357.144t.356-.144M12 12.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 563 B |
@@ -6,6 +6,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { type Unsubscriber } from 'svelte/store';
|
||||
import DatasetInfoForm from './DatasetInfoForm.svelte';
|
||||
import ModelCheckList from './ModelCheckList.svelte';
|
||||
|
||||
let unsubscribeMeta: Unsubscriber | null = null;
|
||||
|
||||
@@ -34,4 +35,5 @@ onDestroy(() => {
|
||||
<DatasetInfoForm />
|
||||
<h2>Environment Check</h2>
|
||||
<hr class="border-zinc-500" />
|
||||
<ModelCheckList />
|
||||
</main>
|
||||
|
||||
@@ -3,11 +3,15 @@ import DatasetName from './meta-form/DatasetName.svelte';
|
||||
import SelectLoraType from './meta-form/SelectLoraType.svelte';
|
||||
import SelectModel from './meta-form/SelectModel.svelte';
|
||||
import TargetSize from './meta-form/TargetSize.svelte';
|
||||
import TriggerWord from './meta-form/TriggerWord.svelte';
|
||||
import UnifiedSizeChoice from './meta-form/UnifiedSizeChoice.svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 px-4 py-4">
|
||||
<div class="grow flex flex-col gap-2 px-4 py-4">
|
||||
<DatasetName />
|
||||
<SelectModel />
|
||||
<SelectLoraType />
|
||||
<UnifiedSizeChoice />
|
||||
<TargetSize />
|
||||
<TriggerWord />
|
||||
</div>
|
||||
|
||||
11
src/routes/dataset/ModelCheckList.svelte
Normal file
11
src/routes/dataset/ModelCheckList.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import FlorenceModel from './model-check/FlorenceModel.svelte';
|
||||
import TagFile from './model-check/TagFile.svelte';
|
||||
import TaggerModel from './model-check/TaggerModel.svelte';
|
||||
</script>
|
||||
|
||||
<div class="min-h-[8em] px-4 py-2 shrink-0 flex flex-row gap-6">
|
||||
<TaggerModel />
|
||||
<FlorenceModel />
|
||||
<TagFile />
|
||||
</div>
|
||||
@@ -66,12 +66,14 @@ function saveTargetSizeDelayed() {
|
||||
<input
|
||||
type="number"
|
||||
class="text-right max-w-[5em]"
|
||||
disabled={!($activeDatasetMeta?.unifiedImageSize ?? true)}
|
||||
bind:value={width}
|
||||
oninput={saveTargetSizeDelayed} />
|
||||
<span>×</span>
|
||||
<input
|
||||
type="number"
|
||||
class="text-right max-w-[5em]"
|
||||
disabled={!($activeDatasetMeta?.unifiedImageSize ?? true)}
|
||||
bind:value={height}
|
||||
oninput={saveTargetSizeDelayed} />
|
||||
<span>px</span>
|
||||
|
||||
141
src/routes/dataset/meta-form/TriggerWord.svelte
Normal file
141
src/routes/dataset/meta-form/TriggerWord.svelte
Normal file
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import TriangleRight from '$lib/components/icons/TriangleRight.svelte';
|
||||
import { activeDatasetMeta, updateActiveDatasetMeta } from '$lib/stores/dataset';
|
||||
import {
|
||||
createDebouncedTrigger,
|
||||
createSaveFeedbackController,
|
||||
type SaveFeedbackState,
|
||||
} from '$lib/utils/form-save';
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let triggerWords = $state<string[]>([]);
|
||||
let saveFeedback = $state<SaveFeedbackState>('idle');
|
||||
let rowFeedback = $state<Record<number, SaveFeedbackState>>({});
|
||||
let editingRowIndex: number | null = null;
|
||||
|
||||
const inputRefs: Array<HTMLInputElement | null> = [];
|
||||
const rowFeedbackTimers = new Map<number, ReturnType<typeof setTimeout>>();
|
||||
|
||||
const feedback = createSaveFeedbackController((state) => {
|
||||
saveFeedback = state;
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
triggerWords = get(activeDatasetMeta)?.triggerWords ?? [];
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
saveLater.cancel();
|
||||
feedback.dispose();
|
||||
|
||||
for (const timer of rowFeedbackTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
rowFeedbackTimers.clear();
|
||||
});
|
||||
|
||||
function markRowFeedback(index: number, state: SaveFeedbackState) {
|
||||
const previous = rowFeedbackTimers.get(index);
|
||||
if (previous) {
|
||||
clearTimeout(previous);
|
||||
}
|
||||
|
||||
rowFeedback = { ...rowFeedback, [index]: state };
|
||||
|
||||
if (state !== 'idle') {
|
||||
const timer = setTimeout(() => {
|
||||
rowFeedback = { ...rowFeedback, [index]: 'idle' };
|
||||
rowFeedbackTimers.delete(index);
|
||||
}, 2000);
|
||||
rowFeedbackTimers.set(index, timer);
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllRowFeedback() {
|
||||
for (const timer of rowFeedbackTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
rowFeedbackTimers.clear();
|
||||
rowFeedback = {};
|
||||
}
|
||||
|
||||
async function persistTriggerWords() {
|
||||
const nextTriggerWords = triggerWords.filter((word) => word.trim() !== '');
|
||||
const removedEmptyRows = nextTriggerWords.length !== triggerWords.length;
|
||||
|
||||
if (removedEmptyRows) {
|
||||
triggerWords = nextTriggerWords;
|
||||
clearAllRowFeedback();
|
||||
editingRowIndex = null;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateActiveDatasetMeta({ triggerWords: [...nextTriggerWords] });
|
||||
feedback.markUpdated();
|
||||
if (editingRowIndex !== null) {
|
||||
markRowFeedback(editingRowIndex, 'updated');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save trigger words:', error);
|
||||
feedback.markNotUpdated();
|
||||
if (editingRowIndex !== null) {
|
||||
markRowFeedback(editingRowIndex, 'not-updated');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saveLater = createDebouncedTrigger(() => {
|
||||
void persistTriggerWords();
|
||||
}, 3000);
|
||||
|
||||
function scheduleSave(index: number | null) {
|
||||
editingRowIndex = index;
|
||||
saveLater.trigger();
|
||||
}
|
||||
|
||||
function onWordInput(index: number, event: Event) {
|
||||
const target = event.currentTarget as HTMLInputElement;
|
||||
triggerWords = triggerWords.map((word, i) => (i === index ? target.value : word));
|
||||
scheduleSave(index);
|
||||
}
|
||||
|
||||
async function addTriggerWord() {
|
||||
const nextIndex = triggerWords.length;
|
||||
triggerWords = [...triggerWords, ''];
|
||||
scheduleSave(nextIndex);
|
||||
|
||||
await tick();
|
||||
inputRefs[nextIndex]?.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<fieldset
|
||||
class={[
|
||||
'fieldset transition-colors duration-300 ease-out',
|
||||
saveFeedback === 'updated' && 'text-green-600',
|
||||
saveFeedback === 'not-updated' && 'text-red-600',
|
||||
]}>
|
||||
<legend class="fieldset-legend">Trigger Word</legend>
|
||||
<ul class="list max-h-[12em] overflow-y-auto">
|
||||
{#each triggerWords as word, index}
|
||||
<li class="list-row py-1 gap-0">
|
||||
<div class="flex justify-center"><TriangleRight width="20" /></div>
|
||||
<input
|
||||
type="text"
|
||||
class={[
|
||||
'input input-ghost input-sm list-col-grow w-full focus:outline-none transition-colors duration-300 ease-out',
|
||||
rowFeedback[index] === 'updated' && 'text-green-600',
|
||||
rowFeedback[index] === 'not-updated' && 'text-red-600',
|
||||
]}
|
||||
bind:value={triggerWords[index]}
|
||||
bind:this={inputRefs[index]}
|
||||
oninput={(event) => onWordInput(index, event)} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button type="button" class="btn btn-sm self-auto" onclick={addTriggerWord}
|
||||
>Add Trigger Word</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
77
src/routes/dataset/meta-form/UnifiedSizeChoice.svelte
Normal file
77
src/routes/dataset/meta-form/UnifiedSizeChoice.svelte
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { activeDatasetMeta, updateActiveDatasetMeta } from '$lib/stores/dataset';
|
||||
import { createSaveFeedbackController, type SaveFeedbackState } from '$lib/utils/form-save';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let unifiedSize = $state<boolean>(false);
|
||||
let unifiedRatio = $state<boolean>(false);
|
||||
let sizeFeedback = $state<SaveFeedbackState>('idle');
|
||||
let ratioFeedback = $state<SaveFeedbackState>('idle');
|
||||
|
||||
const sizeSaveFeedback = createSaveFeedbackController((state) => {
|
||||
sizeFeedback = state;
|
||||
});
|
||||
|
||||
const ratioSaveFeedback = createSaveFeedbackController((state) => {
|
||||
ratioFeedback = state;
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
const meta = get(activeDatasetMeta);
|
||||
unifiedSize = meta?.unifiedImageSize ?? false;
|
||||
unifiedRatio = meta?.unifiedImageRatio ?? false;
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
sizeSaveFeedback.dispose();
|
||||
ratioSaveFeedback.dispose();
|
||||
});
|
||||
|
||||
async function saveUnifiedSize() {
|
||||
try {
|
||||
await updateActiveDatasetMeta({ unifiedImageSize: unifiedSize });
|
||||
sizeSaveFeedback.markUpdated();
|
||||
} catch (error) {
|
||||
console.error('Failed to save unified size option:', error);
|
||||
sizeSaveFeedback.markNotUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveUnifiedRatio() {
|
||||
try {
|
||||
await updateActiveDatasetMeta({ unifiedImageRatio: unifiedRatio });
|
||||
ratioSaveFeedback.markUpdated();
|
||||
} catch (error) {
|
||||
console.error('Failed to save unified ratio option:', error);
|
||||
ratioSaveFeedback.markNotUpdated();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-3 h-(--size) py-2">
|
||||
<div class="min-w-[10em]"> </div>
|
||||
<label
|
||||
class={[
|
||||
'label transition-colors duration-300 ease-out',
|
||||
sizeFeedback === 'updated' && 'text-green-600',
|
||||
sizeFeedback === 'not-updated' && 'text-red-600',
|
||||
]}>
|
||||
<input type="checkbox" bind:checked={unifiedSize} onchange={saveUnifiedSize} class="toggle" />
|
||||
Unified Size
|
||||
</label>
|
||||
<label
|
||||
class={[
|
||||
'label transition-colors duration-300 ease-out',
|
||||
ratioFeedback === 'updated' && 'text-green-600',
|
||||
ratioFeedback === 'not-updated' && 'text-red-600',
|
||||
]}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={unifiedRatio}
|
||||
onchange={saveUnifiedRatio}
|
||||
disabled={!unifiedSize}
|
||||
class="toggle" />
|
||||
Unified Ratio
|
||||
</label>
|
||||
</div>
|
||||
5
src/routes/dataset/model-check/FlorenceModel.svelte
Normal file
5
src/routes/dataset/model-check/FlorenceModel.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import ModelItem from './ModelItem.svelte';
|
||||
</script>
|
||||
|
||||
<ModelItem status="ok">Florence 2</ModelItem>
|
||||
39
src/routes/dataset/model-check/ModelItem.svelte
Normal file
39
src/routes/dataset/model-check/ModelItem.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import Check from '$lib/components/icons/Check.svelte';
|
||||
import Close from '$lib/components/icons/Close.svelte';
|
||||
import Warning from '$lib/components/icons/Warning.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
type ModelItemProps = {
|
||||
status: 'ok' | 'unexists' | 'downloading' | 'error';
|
||||
progress?: number;
|
||||
children?: Snippet;
|
||||
};
|
||||
|
||||
let { status, progress, children }: ModelItemProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-center w-32 gap-3">
|
||||
<div
|
||||
class={[
|
||||
'radial-progress',
|
||||
status === 'ok' && 'text-green-500',
|
||||
status === 'unexists' && 'text-orange-500',
|
||||
status === 'downloading' && 'text-blue-500',
|
||||
status === 'error' && 'text-red-500',
|
||||
]}
|
||||
style:--value={status === 'downloading' ? (progress ?? 0) : 100}
|
||||
style:--size="4rem"
|
||||
role="progressbar">
|
||||
{#if status === 'downloading' && progress !== undefined}
|
||||
{progress}%
|
||||
{:else if status === 'ok'}
|
||||
<Check width="30" />
|
||||
{:else if status === 'unexists'}
|
||||
<Warning width="30" />
|
||||
{:else if status === 'error'}
|
||||
<Close width="30" />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="text-center">{@render children?.()}</span>
|
||||
</div>
|
||||
5
src/routes/dataset/model-check/TagFile.svelte
Normal file
5
src/routes/dataset/model-check/TagFile.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import ModelItem from './ModelItem.svelte';
|
||||
</script>
|
||||
|
||||
<ModelItem status="ok">Tag File</ModelItem>
|
||||
5
src/routes/dataset/model-check/TaggerModel.svelte
Normal file
5
src/routes/dataset/model-check/TaggerModel.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import ModelItem from './ModelItem.svelte';
|
||||
</script>
|
||||
|
||||
<ModelItem status="ok">WD ViT Tagger</ModelItem>
|
||||
Reference in New Issue
Block a user