refactor: Icons
This commit is contained in:
parent
b1cf8bb814
commit
23a91cd257
|
|
@ -4,7 +4,7 @@
|
|||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants/modality-icons';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants/icons';
|
||||
import { FileTypeCategory } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { Eye, Mic } from '@lucide/svelte';
|
||||
import { ModelModality } from '$lib/enums';
|
||||
import { MODALITY_ICONS, MODALITY_LABELS } from '$lib/constants/icons';
|
||||
import { cn } from '$lib/components/ui/utils';
|
||||
|
||||
type DisplayableModality = ModelModality.VISION | ModelModality.AUDIO;
|
||||
|
||||
interface Props {
|
||||
modalities: ModelModality[];
|
||||
class?: string;
|
||||
|
|
@ -10,31 +12,17 @@
|
|||
|
||||
let { modalities, class: className = '' }: Props = $props();
|
||||
|
||||
function getModalityIcon(modality: ModelModality) {
|
||||
switch (modality) {
|
||||
case ModelModality.VISION:
|
||||
return Eye;
|
||||
case ModelModality.AUDIO:
|
||||
return Mic;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getModalityLabel(modality: ModelModality): string {
|
||||
switch (modality) {
|
||||
case ModelModality.VISION:
|
||||
return 'Vision';
|
||||
case ModelModality.AUDIO:
|
||||
return 'Audio';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
// Filter to only modalities that have icons (VISION, AUDIO)
|
||||
const displayableModalities = $derived(
|
||||
modalities.filter(
|
||||
(m): m is DisplayableModality => m === ModelModality.VISION || m === ModelModality.AUDIO
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
{#each modalities as modality, index (index)}
|
||||
{@const IconComponent = getModalityIcon(modality)}
|
||||
{#each displayableModalities as modality, index (index)}
|
||||
{@const IconComponent = MODALITY_ICONS[modality]}
|
||||
{@const label = MODALITY_LABELS[modality]}
|
||||
|
||||
<span
|
||||
class={cn(
|
||||
|
|
@ -46,6 +34,6 @@
|
|||
<IconComponent class="h-3 w-3" />
|
||||
{/if}
|
||||
|
||||
{getModalityLabel(modality)}
|
||||
{label}
|
||||
</span>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@
|
|||
modelsUpdating,
|
||||
selectModel,
|
||||
selectedModelId,
|
||||
modelsStore,
|
||||
unloadModel
|
||||
unloadModel,
|
||||
routerModels,
|
||||
loadingModelIds,
|
||||
loadModel
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { isRouterMode, propsStore } from '$lib/stores/props.svelte';
|
||||
import { DialogModelInformation } from '$lib/components/app';
|
||||
import { MODALITY_ICONS } from '$lib/constants/modality-icons';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -44,6 +46,22 @@
|
|||
let isRouter = $derived(isRouterMode());
|
||||
let serverModel = $derived(propsStore.modelName);
|
||||
|
||||
// Reactive router models state - needed for proper reactivity of isModelLoaded checks
|
||||
let currentRouterModels = $derived(routerModels());
|
||||
let currentLoadingModelIds = $derived(loadingModelIds());
|
||||
|
||||
// Helper functions that create reactive dependencies
|
||||
function checkIsModelLoaded(modelId: string): boolean {
|
||||
// Access currentRouterModels to establish reactive dependency
|
||||
const model = currentRouterModels.find((m) => m.name === modelId);
|
||||
return model?.status?.value === ServerModelStatus.LOADED || false;
|
||||
}
|
||||
|
||||
function checkIsModelOperationInProgress(modelId: string): boolean {
|
||||
// Access currentLoadingModelIds to establish reactive dependency
|
||||
return currentLoadingModelIds.includes(modelId);
|
||||
}
|
||||
|
||||
let isHighlightedCurrentModelActive = $derived(
|
||||
!isRouter || !currentModel
|
||||
? false
|
||||
|
|
@ -240,16 +258,28 @@
|
|||
};
|
||||
}
|
||||
|
||||
function handleSelect(modelId: string) {
|
||||
async function handleSelect(modelId: string) {
|
||||
const option = options.find((opt) => opt.id === modelId);
|
||||
if (option && onModelChange) {
|
||||
if (!option) return;
|
||||
|
||||
closeMenu();
|
||||
|
||||
if (onModelChange) {
|
||||
// If callback provided, use it (for regenerate functionality)
|
||||
onModelChange(option.id, option.model);
|
||||
} else if (option) {
|
||||
// Otherwise, just update the global selection (for form selector)
|
||||
selectModel(option.id).catch(console.error);
|
||||
} else {
|
||||
// Update global selection
|
||||
await selectModel(option.id);
|
||||
}
|
||||
|
||||
// Load the model if not already loaded (router mode)
|
||||
if (isRouter && !checkIsModelLoaded(option.model)) {
|
||||
try {
|
||||
await loadModel(option.model);
|
||||
} catch (error) {
|
||||
console.error('Failed to load model:', error);
|
||||
}
|
||||
}
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
function getDisplayOption(): ModelOption | undefined {
|
||||
|
|
@ -382,10 +412,8 @@
|
|||
<div class="my-1 h-px bg-border"></div>
|
||||
{/if}
|
||||
{#each options as option (option.id)}
|
||||
{@const isLoaded = modelsStore.isModelLoaded(option.model)}
|
||||
{@const isUnloading = modelsStore.isModelOperationInProgress(option.model)}
|
||||
{@const hasVision = option.modalities?.vision ?? false}
|
||||
{@const hasAudio = option.modalities?.audio ?? false}
|
||||
{@const isLoaded = checkIsModelLoaded(option.model)}
|
||||
{@const isUnloading = checkIsModelOperationInProgress(option.model)}
|
||||
{@const isSelected = currentModel === option.model || activeId === option.id}
|
||||
<div
|
||||
class={cn(
|
||||
|
|
@ -408,28 +436,13 @@
|
|||
>
|
||||
<span class="min-w-0 flex-1 truncate">{option.model}</span>
|
||||
|
||||
<!-- <div class="flex shrink-0 items-center gap-2"> -->
|
||||
<MODALITY_ICONS.vision
|
||||
class={cn(
|
||||
'h-3.5 w-3.5',
|
||||
hasVision ? 'text-foreground' : 'text-muted-foreground/40'
|
||||
)}
|
||||
/>
|
||||
<MODALITY_ICONS.audio
|
||||
class={cn(
|
||||
'h-3.5 w-3.5',
|
||||
hasAudio ? 'text-foreground' : 'text-muted-foreground/40'
|
||||
)}
|
||||
/>
|
||||
<!-- </div> -->
|
||||
|
||||
{#if isUnloading}
|
||||
<Loader2 class="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
||||
{:else if isLoaded}
|
||||
<!-- Green dot, on hover show red unload button -->
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex h-4 w-4 shrink-0 items-center justify-center"
|
||||
class="relative ml-2 flex h-4 w-4 shrink-0 items-center justify-center"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
unloadModel(option.model);
|
||||
|
|
@ -437,14 +450,15 @@
|
|||
title="Unload model"
|
||||
>
|
||||
<span
|
||||
class="h-2 w-2 rounded-full bg-green-500 transition-opacity group-hover:opacity-0"
|
||||
class="mr-2 h-2 w-2 rounded-full bg-green-500 transition-opacity group-hover:opacity-0"
|
||||
></span>
|
||||
|
||||
<Power
|
||||
class="absolute h-4 w-4 text-red-500 opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-600"
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="mr-1 h-2 w-2 shrink-0 rounded-full bg-muted-foreground/50"></span>
|
||||
<span class="mx-2 h-2 w-2 shrink-0 rounded-full bg-muted-foreground/50"></span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import {
|
|||
File as FileIcon,
|
||||
FileText as FileTextIcon,
|
||||
Image as ImageIcon,
|
||||
Volume2 as AudioIcon
|
||||
Eye as VisionIcon,
|
||||
Mic as AudioIcon
|
||||
} from '@lucide/svelte';
|
||||
import { FileTypeCategory } from '$lib/enums';
|
||||
import { FileTypeCategory, ModelModality } from '$lib/enums';
|
||||
|
||||
export const FILE_TYPE_ICONS = {
|
||||
[FileTypeCategory.IMAGE]: ImageIcon,
|
||||
|
|
@ -20,9 +21,12 @@ export const FILE_TYPE_ICONS = {
|
|||
|
||||
export const DEFAULT_FILE_ICON = FileIcon;
|
||||
|
||||
export type ModelModality = 'vision' | 'audio';
|
||||
|
||||
export const MODALITY_ICONS = {
|
||||
vision: ImageIcon,
|
||||
audio: AudioIcon
|
||||
[ModelModality.VISION]: VisionIcon,
|
||||
[ModelModality.AUDIO]: AudioIcon
|
||||
} as const;
|
||||
|
||||
export const MODALITY_LABELS = {
|
||||
[ModelModality.VISION]: 'Vision',
|
||||
[ModelModality.AUDIO]: 'Audio'
|
||||
} as const;
|
||||
Loading…
Reference in New Issue