7a944afdb5
Fixes link color from hardcoded white to currentColor for proper theme support. Adds video and audio modality icons to ModelInfo. Registers anthropic, nvidia, and xiaomi in the provider icon map. Removes unused pagination validation from agents and topics endpoints. Adds convenience start script. Updates AGENTS.md.
140 lines
6.8 KiB
Vue
140 lines
6.8 KiB
Vue
<script setup lang="ts">
|
|
import { type Model } from '~/composables/useModels';
|
|
|
|
const { updateModel, deleteModel } = await useModels();
|
|
|
|
const props = withDefaults(defineProps<{
|
|
model: Model;
|
|
size?: 'small' | 'medium' | 'large';
|
|
details?: boolean;
|
|
showCost?: boolean;
|
|
showEdit?: boolean;
|
|
showExternalId?: boolean;
|
|
showReleaseDate?: boolean;
|
|
}>(), {
|
|
size: 'large',
|
|
details: false,
|
|
showCost: false,
|
|
showExternalId: false,
|
|
showReleaseDate: false,
|
|
showEdit: false,
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
edit: [model: Model];
|
|
}>();
|
|
|
|
const formatContextWindow = (window: number | null | undefined): string => {
|
|
if (!window) return '';
|
|
if (window >= 1000000) return `${(window / 1000000).toFixed(0)}M`;
|
|
if (window >= 1000) return `${(window / 1000).toFixed(0)}K`;
|
|
return window.toString();
|
|
};
|
|
|
|
const showCost = computed(() => {
|
|
return props.showCost && (props.model.cost.prompt || props.model.cost.completion || props.model.cost.request);
|
|
});
|
|
|
|
const toggleModel = async (id: string) => {
|
|
await updateModel(id, {
|
|
enabled: !props.model.enabled,
|
|
});
|
|
};
|
|
|
|
const handleEdit = () => {
|
|
emit('edit', props.model);
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex items-center justify-between w-full" v-bind="$attrs">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<ModelIcon :class="{
|
|
'rounded-lg overflow-hidden': size === 'large',
|
|
'rounded-md overflow-hidden': size === 'medium' || size === 'small',
|
|
}" :avatar="true" variant="color" :key="model.externalId" :model-id="model.externalId"
|
|
:size="size === 'small' ? '20' : size === 'medium' ? '26' : '32'" />
|
|
|
|
<div class="flex flex-col gap-1 min-w-0 flex-1">
|
|
<div class="flex items-center gap-1 min-w-0">
|
|
<span class="text-[15px] font-medium text-[var(--text-primary)] truncate min-w-0">
|
|
{{ model.name }}
|
|
</span>
|
|
<span v-if="showExternalId" :title="model.externalId"
|
|
class="text-xs text-[var(--text-secondary)] px-1 py-0.5 rounded bg-[var(--bg-container)] whitespace-nowrap truncate max-w-[240px]">
|
|
{{ model.externalId }}
|
|
</span>
|
|
<div v-if="showEdit" class="flex items-center gap-2">
|
|
<!-- Edit button - only for custom models -->
|
|
<button v-if="model.isCustom" @click="handleEdit"
|
|
class="rounded h-5 w-5 flex items-center justify-center opacity-0 group-hover:opacity-100 @hover:bg-[var(--color-accent)]/30 text-[var(--color-accent)] transition-all duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
|
<span class="i-mynaui-pencil text-3.5"></span>
|
|
</button>
|
|
<button @click="deleteModel(model.id)"
|
|
class="rounded h-5 w-5 flex items-center justify-center opacity-0 group-hover:opacity-100 @hover:bg-red-500/30 text-red-500 transition-all duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
|
<span class="i-mynaui-trash text-3.5"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="details" class="flex items-center gap-1.5 flex-wrap text-[var(--text-tertiary)]">
|
|
<span v-if="showReleaseDate && model.releasedAt" class="text-xs whitespace-nowrap">
|
|
Released on {{
|
|
typeof model.releasedAt === 'string'
|
|
? (model.releasedAt as string).split('T')[0]
|
|
: model.releasedAt.toISOString().split('T')[0]
|
|
}}
|
|
</span>
|
|
<template v-if="showCost">
|
|
<span v-if="model.cost.prompt" class="text-xs whitespace-nowrap flex items-center">
|
|
<span class="w-1 h-1 rounded-full bg-[var(--text-secondary)] inline-block mr-1"></span>
|
|
${{ model.cost.prompt }}/M input
|
|
</span>
|
|
<span v-if="model.cost.completion" class="text-xs whitespace-nowrap flex items-center">
|
|
<span class="w-1 h-1 rounded-full bg-[var(--text-secondary)] inline-block mr-1"></span>
|
|
${{ model.cost.completion }}/M output
|
|
</span>
|
|
<span v-if="model.cost.request && model.cost.request !== '0'"
|
|
class="text-xs whitespace-nowrap flex items-center">
|
|
<span class="w-1 h-1 rounded-full bg-[var(--text-secondary)] inline-block mr-1"></span>
|
|
${{ model.cost.request }}/K request
|
|
</span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-1 shrink-0">
|
|
<div class="flex items-center gap-0.5">
|
|
<div v-if="model.inputModalities.includes('image')" title="Vision"
|
|
class="w-4.5 h-4.5 bg-emerald/10 rounded flex items-center justify-center">
|
|
<span class="i-mynaui-image text-2.5 text-emerald"></span>
|
|
</div>
|
|
<div v-if="model.inputModalities.includes('video')" title="Video Input"
|
|
class="w-4.5 h-4.5 bg-pink/10 rounded flex items-center justify-center">
|
|
<span class="i-mynaui-video text-2.5 text-pink"></span>
|
|
</div>
|
|
<div v-if="model.inputModalities.includes('audio')" title="Audio Input"
|
|
class="w-4.5 h-4.5 bg-orange/10 rounded flex items-center justify-center">
|
|
<span class="i-mynaui-volume-high text-2.5 text-orange"></span>
|
|
</div>
|
|
<div v-if="model.capabilities.includes('reasoning')" title="Reasoning"
|
|
class="w-4.5 h-4.5 bg-[color-mix(in_srgb,_transparent_90%,_var(--reasoning-accent)_10%)] rounded flex items-center justify-center">
|
|
<span class="i-mynaui-atom text-2.5 text-[var(--reasoning-accent)]"></span>
|
|
</div>
|
|
<div v-if="model.capabilities.includes('tools')" title="Tools"
|
|
class="w-4.5 h-4.5 bg-emerald/10 rounded flex items-center justify-center">
|
|
<span class="i-mynaui-tool text-2.5 text-sky"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<span v-if="model.contextWindow"
|
|
class="text-xs font-mono text-[var(--text-secondary)] px-1.5 py-0.5 rounded bg-[var(--bg-container)]">
|
|
{{ formatContextWindow(model.contextWindow) }}
|
|
</span>
|
|
|
|
<Slider v-if="showEdit" :checked="model.enabled" @click="toggleModel(model.id)" />
|
|
</div>
|
|
</div>
|
|
</template>
|