refactor: Icons

This commit is contained in:
Aleksander Grygier 2025-11-26 14:13:17 +01:00
parent b1cf8bb814
commit 23a91cd257
4 changed files with 69 additions and 63 deletions

View File

@ -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 {

View File

@ -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}

View File

@ -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}

View File

@ -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;