mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
feat(models): sort catalog by listing date availability and price
This commit is contained in:
@@ -73,6 +73,7 @@ import ReasoningLevelPicker, {
|
||||
REASONING_LEVELS,
|
||||
} from '@/app/home/components/reasoning/ReasoningLevelPicker';
|
||||
import LangBotModelMetadata from '@/app/home/components/model-availability/LangBotModelMetadata';
|
||||
import { sortModelsByCatalog } from '@/app/home/components/model-availability/sort-models';
|
||||
import { useLangBotModelAvailability } from '@/app/home/components/model-availability/useLangBotModelAvailability';
|
||||
|
||||
const MODEL_SELECT_TRIGGER_CLASS =
|
||||
@@ -584,8 +585,11 @@ export default function DynamicFormItemComponent({
|
||||
|
||||
case DynamicFormItemType.LLM_MODEL_SELECTOR:
|
||||
// Separate space models from regular models
|
||||
const spaceModels = llmModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
const spaceModels = sortModelsByCatalog(
|
||||
llmModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
),
|
||||
langbotModelMetadata,
|
||||
);
|
||||
const regularModels = llmModels.filter(
|
||||
(m) => m.provider?.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
@@ -778,8 +782,11 @@ export default function DynamicFormItemComponent({
|
||||
);
|
||||
|
||||
case DynamicFormItemType.EMBEDDING_MODEL_SELECTOR: {
|
||||
const spaceEmbeddingModels = embeddingModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
const spaceEmbeddingModels = sortModelsByCatalog(
|
||||
embeddingModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
),
|
||||
langbotModelMetadata,
|
||||
);
|
||||
const regularEmbeddingModels = embeddingModels.filter(
|
||||
(m) => m.provider?.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
@@ -977,6 +984,18 @@ export default function DynamicFormItemComponent({
|
||||
},
|
||||
{} as Record<string, RerankModel[]>,
|
||||
);
|
||||
for (const [providerName, models] of Object.entries(
|
||||
groupedRerankModels,
|
||||
)) {
|
||||
if (
|
||||
models[0]?.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER
|
||||
) {
|
||||
groupedRerankModels[providerName] = sortModelsByCatalog(
|
||||
models,
|
||||
langbotModelMetadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md min-w-0">
|
||||
@@ -1012,8 +1031,11 @@ export default function DynamicFormItemComponent({
|
||||
|
||||
case DynamicFormItemType.MODEL_FALLBACK_SELECTOR: {
|
||||
// Separate space models from regular models
|
||||
const fbSpaceModels = llmModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
const fbSpaceModels = sortModelsByCatalog(
|
||||
llmModels.filter(
|
||||
(m) => m.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
),
|
||||
langbotModelMetadata,
|
||||
);
|
||||
const fbRegularModels = llmModels.filter(
|
||||
(m) => m.provider?.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { LangBotModelAvailabilityItem } from '@/app/infra/entities/api';
|
||||
|
||||
type CatalogModel = { uuid: string; name: string };
|
||||
type MetadataMap = Record<string, LangBotModelAvailabilityItem>;
|
||||
|
||||
function listingDay(value?: string | null): number {
|
||||
const timestamp = value ? Date.parse(value) : NaN;
|
||||
// Use UTC calendar days so list order is consistent across user time zones.
|
||||
return Number.isFinite(timestamp) ? Math.floor(timestamp / 86_400_000) : -1;
|
||||
}
|
||||
|
||||
function availabilityRank(up?: boolean | null): number {
|
||||
return up === true ? 0 : up == null ? 1 : 2;
|
||||
}
|
||||
|
||||
function price(value?: number | null): number {
|
||||
return value != null && Number.isFinite(value) && value >= 0
|
||||
? value
|
||||
: Infinity;
|
||||
}
|
||||
|
||||
/** Sort one LangBot Models group without changing the source array. */
|
||||
export function sortModelsByCatalog<T extends CatalogModel>(
|
||||
models: readonly T[],
|
||||
metadata: MetadataMap,
|
||||
): T[] {
|
||||
// Keep the existing order until catalog metadata is available.
|
||||
if (Object.keys(metadata).length === 0) return [...models];
|
||||
|
||||
return [...models].sort((left, right) => {
|
||||
const a = metadata[left.uuid] ?? metadata[left.name];
|
||||
const b = metadata[right.uuid] ?? metadata[right.name];
|
||||
const dateOrder = listingDay(b?.listed_at) - listingDay(a?.listed_at);
|
||||
if (dateOrder) return dateOrder;
|
||||
|
||||
const statusOrder =
|
||||
availabilityRank(a?.availability?.up) -
|
||||
availabilityRank(b?.availability?.up);
|
||||
if (statusOrder) return statusOrder;
|
||||
|
||||
for (const key of ['input_credits', 'output_credits'] as const) {
|
||||
const aPrice = price(a?.[key]);
|
||||
const bPrice = price(b?.[key]);
|
||||
if (aPrice !== bPrice) return aPrice < bPrice ? -1 : 1;
|
||||
}
|
||||
return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
||||
});
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
ProviderModels,
|
||||
} from '../types';
|
||||
import ModelItem from './ModelItem';
|
||||
import { sortModelsByCatalog } from '../../model-availability/sort-models';
|
||||
import AddModelPopover from './AddModelPopover';
|
||||
|
||||
interface ProviderCardProps {
|
||||
@@ -425,7 +426,10 @@ export default function ProviderCard({
|
||||
</p>
|
||||
) : models ? (
|
||||
<div className="space-y-2">
|
||||
{models.llm.map((model) => (
|
||||
{(isLangBotModels
|
||||
? sortModelsByCatalog(models.llm, modelMetadata)
|
||||
: models.llm
|
||||
).map((model) => (
|
||||
<ModelItem
|
||||
key={model.uuid}
|
||||
model={model}
|
||||
@@ -480,7 +484,10 @@ export default function ProviderCard({
|
||||
onResetTestResult={onResetTestResult}
|
||||
/>
|
||||
))}
|
||||
{models.embedding.map((model) => (
|
||||
{(isLangBotModels
|
||||
? sortModelsByCatalog(models.embedding, modelMetadata)
|
||||
: models.embedding
|
||||
).map((model) => (
|
||||
<ModelItem
|
||||
key={model.uuid}
|
||||
model={model}
|
||||
@@ -533,7 +540,10 @@ export default function ProviderCard({
|
||||
onResetTestResult={onResetTestResult}
|
||||
/>
|
||||
))}
|
||||
{models.rerank.map((model) => (
|
||||
{(isLangBotModels
|
||||
? sortModelsByCatalog(models.rerank, modelMetadata)
|
||||
: models.rerank
|
||||
).map((model) => (
|
||||
<ModelItem
|
||||
key={model.uuid}
|
||||
model={model}
|
||||
|
||||
Reference in New Issue
Block a user