"use client";
import { useEffect, useMemo, useState } from "react";
import { authFetch } from "@/lib/auth/client";
import {
AUTO_MODEL,
parseChatModelsResponse,
type ChatModel,
} from "@/lib/chat/models";
import type { ModelId } from "@/lib/chat/types";
export type ModelsStatus = "loading" | "ready" | "unavailable";
export type UseModelsResult = {
status: ModelsStatus;
models: ChatModel[];
effectiveModelId: ModelId;
};
const FALLBACK_MODELS: ChatModel[] = [AUTO_MODEL];
export function useModels(
userId: string,
persistedModelId: ModelId,
onUnauthorized?: () => void,
): UseModelsResult {
const [state, setState] = useState<{
userId: string;
status: ModelsStatus;
models: ChatModel[];
}>({
userId,
status: "loading",
models: FALLBACK_MODELS,
});
useEffect(() => {
const controller = new AbortController();
let active = true;
void authFetch("/api/models", { cache: "no-store", signal: controller.signal })
.then(async (response) => {
if (!active) return;
if (response.status === 401) {
onUnauthorized?.();
setState({ userId, status: "unavailable", models: FALLBACK_MODELS });
return;
}
if (!response.ok) throw new Error("catalog unavailable");
const models = parseChatModelsResponse(await response.json());
if (!models) throw new Error("invalid catalog");
if (active) setState({ userId, status: "ready", models });
})
.catch(() => {
if (active && !controller.signal.aborted) {
setState({ userId, status: "unavailable", models: FALLBACK_MODELS });
}
});
return () => {
active = false;
controller.abort();
};
}, [onUnauthorized, userId]);
const current =
state.userId === userId
? state
: { userId, status: "loading" as const, models: FALLBACK_MODELS };
const effectiveModelId = useMemo(
() =>
current.models.some((model) => model.id === persistedModelId)
? persistedModelId
: "auto",
[current.models, persistedModelId],
);
return { status: current.status, models: current.models, effectiveModelId };
}