diff --git a/src/modules/llms/server/ollama/ollama.router.ts b/src/modules/llms/server/ollama/ollama.router.ts index f904d95b6..1aed48c94 100644 --- a/src/modules/llms/server/ollama/ollama.router.ts +++ b/src/modules/llms/server/ollama/ollama.router.ts @@ -112,8 +112,9 @@ const adminPullModelSchema = z.object({ name: z.string(), }); +// this may not be needed const listPullableOutputSchema = z.object({ - pullable: z.array(z.object({ + pullableModels: z.array(z.object({ id: z.string(), label: z.string(), tag: z.string(), @@ -133,7 +134,7 @@ export const llmOllamaRouter = createTRPCRouter({ .output(listPullableOutputSchema) .query(async ({}) => { return { - pullable: Object.entries(OLLAMA_BASE_MODELS).map(([model_id, model]) => ({ + pullableModels: Object.entries(OLLAMA_BASE_MODELS).map(([model_id, model]) => ({ id: model_id, label: capitalizeFirstLetter(model_id), tag: 'latest', diff --git a/src/modules/llms/vendors/ollama/OllamaAdministration.tsx b/src/modules/llms/vendors/ollama/OllamaAdministration.tsx index 46499bd8c..96cfec57a 100644 --- a/src/modules/llms/vendors/ollama/OllamaAdministration.tsx +++ b/src/modules/llms/vendors/ollama/OllamaAdministration.tsx @@ -15,15 +15,20 @@ import type { OllamaAccessSchema } from '../../server/ollama/ollama.router'; // configuration -const FALLBACK_PRESELECT_MODEL = 'llama3'; +const FALLBACK_PRESELECT_MODEL = 'llama3.3'; +const _stableNoPullable = [] as const; +const _stableNoPullableTags = [] as const; + export function OllamaAdministration(props: { access: OllamaAccessSchema, onClose: () => void }) { // state const [sortByPulls, setSortByPulls] = React.useState(false); - const [_modelName, setModelName] = React.useState(null); - const [modelTag, setModelTag] = React.useState(''); + const [_selectedModelName, setSelectedModelName] = React.useState(null); + // state for the autocomplete component + const [modelTagValue, setModelTagValue] = React.useState(null); + const [modelTagInputValue, setModelTagInputValue] = React.useState(''); // external state const { data: pullableData } = apiQuery.llmOllama.adminListPullable.useQuery({ access: props.access }, { @@ -33,29 +38,36 @@ export function OllamaAdministration(props: { access: OllamaAccessSchema, onClos const { isPending: isDeleting, status: deleteStatus, error: deleteError, mutate: deleteMutate, reset: deleteReset } = apiQuery.llmOllama.adminDelete.useMutation(); // derived state - const modelName = _modelName || pullableData?.pullable?.[0]?.id || FALLBACK_PRESELECT_MODEL; - let pullable = pullableData?.pullable || []; - if (sortByPulls) - pullable = pullable.toSorted((a, b) => b.pulls - a.pulls); - const pullModelDescription = pullable.find(p => p.id === modelName)?.description ?? null; - + const selectedModelName = _selectedModelName // user selected + || pullableData?.pullableModels?.[0]?.id // or the first in the list + || FALLBACK_PRESELECT_MODEL; // or a fallback const handleModelPull = React.useCallback(() => { deleteReset(); - modelName && pullMutate({ access: props.access, name: modelName + (modelTag ? ':' + modelTag : '') }); - }, [modelName, modelTag, pullMutate, props.access, deleteReset]); + selectedModelName && pullMutate({ access: props.access, name: selectedModelName + (modelTagInputValue ? ':' + modelTagInputValue : '') }); + }, [selectedModelName, modelTagInputValue, pullMutate, props.access, deleteReset]); const handleModelDelete = React.useCallback(() => { pullReset(); - modelName && deleteMutate({ access: props.access, name: modelName + (modelTag ? ':' + modelTag : '') }); - }, [modelName, modelTag, deleteMutate, props.access, pullReset]); + selectedModelName && deleteMutate({ access: props.access, name: selectedModelName + (modelTagInputValue ? ':' + modelTagInputValue : '') }); + }, [selectedModelName, modelTagInputValue, deleteMutate, props.access, pullReset]); + + // stabilize the derived arrays + const { pullableModels, pullModelTags, pullModelDescription } = React.useMemo(() => { + // optionally sort models by pulls + let pullable = pullableData?.pullableModels || _stableNoPullable; + if (sortByPulls) + pullable = pullable.toSorted((a, b) => b.pulls - a.pulls); - // memo 'tags' for the autocomplete for the currently selected model - const pullableTagsMemo = React.useMemo(() => { - const model = pullable.find(p => p.id === modelName); - return model?.tags || []; - }, [pullable, modelName]); + // return the tags and description for the selected model + const selectedModel = pullable.find(p => p.id === selectedModelName) ?? null; + return { + pullableModels: pullable, + pullModelDescription: selectedModel?.description || '', + pullModelTags: selectedModel?.tags || _stableNoPullableTags, + }; + }, [pullableData?.pullableModels, sortByPulls, selectedModelName]); return ( @@ -69,14 +81,14 @@ export function OllamaAdministration(props: { access: OllamaAccessSchema, onClos - +