Skip to content

Model selector

A <aparte-model-selector> web component that lets the user pick an AI provider and model — a grouped dropdown built on core’s <aparte-select>, for the BYOK (Bring Your Own Key) pattern.

Terminal window
npm install @aparte/plugin-model-selector @aparte/core

@aparte/core is the only peer dependency. Importing the package registers the element as a side effect:

import '@aparte/plugin-model-selector';
<aparte-model-selector auto-select persist searchable></aparte-model-selector>

Each registered provider becomes an <aparte-optgroup>; a single provider renders a flat list.

AttributeEffect
auto-selectSelect the first available model on mount
persistWrite the selection back to the resolved config
searchableEnable search in the dropdown
placeholderOverride the placeholder (else the active locale string)
disabledDisable the picker; inside an <aparte-composer> it also follows the composer’s disabled

It fires aparte-model-change with { providerId, modelId, previousProviderId, previousModelId }. The selector reads providers from the nearest instance config (via attachConfig), falling back to the global aparteGlobalConfig — so multi-chat pages each drive their own model list.

In the composer’s bottom row — that is what the row is for, and it is one element:

<aparte-composer-toolbar>
<aparte-model-selector style="margin-inline-start: auto"></aparte-model-selector>
</aparte-composer-toolbar>

margin-inline-start: auto pushes it to the end of the row; drop it and the selector sits at the start. In the four wrappers the same thing goes through one toolbar slot — see The composer toolbar.

The element is typed by this package, not by the framework wrappers, through one subpath per framework. Import that subpath once, anywhere in your app, and the tag is checked like any other: an unknown attribute or a wrong value type is a compile error.

Nothing to import for types — the tag is in HTMLElementTagNameMap as soon as the plugin itself is imported, and its event is typed through HTMLElementEventMap.

<aparte-model-selector persist searchable placeholder="Pick a model"></aparte-model-selector>

Outside Angular a presence attribute is '' to set and null to remove, never false — see the rule. Events are typed through the DOM, because @aparte/core augments HTMLElementEventMap with aparte-model-change.

react, vue, svelte and @angular/core are all optional peer dependencies, so you carry only the one you use — and the three template bindings ship no runtime at all.

Gating the composer until a model is picked

Section titled “Gating the composer until a model is picked”

The model list loads asynchronously, so there’s a window where the chat is mounted but no model is selected yet. Opt in to block sending (and grey out <aparte-composer>) until one is:

import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setRequireModelSelection(true);

The composer re-enables automatically once auto-select (or the user) picks a model. Off by default, so single-model or backend-driven setups that never select a model are unaffected.

Generated from the plugin’s own custom-elements manifest — the same file that feeds editor autocomplete when you install the package.

Example

<!-- Mount it in the composer's toolbar; it reads the models the config already knows. -->
<aparte-composer-toolbar>
<aparte-model-selector searchable placeholder="Pick a model"></aparte-model-selector>
</aparte-composer-toolbar>

Attributes

AttributeDescription
persistWrites the selection back through the config, so it survives a reload.
auto-selectSelects the first model as soon as one is available.
searchableAdds the dropdown’s filter field.
placeholderOverrides the text shown while nothing is selected.
disabledDisables the picker. Inside an <aparte-composer> it also follows the composer’s own disabled, so a disabled composer does not leave its model picker operable.

Properties

PropertyTypeDescription
providerId (readonly)string | nullCurrent provider ID.
modelId (readonly)string | nullCurrent model ID.

Methods

MethodDescription
aparteConfigChanged(next: AparteConfig): voidThe boundary above us appeared or changed: move the subscription and reload, because a different config means different providers and a different selection. See AparteConfigAware. The element’s own per-instance tests passed before this existed only because they mount the boundary FIRST — the one ordering no wrapper produces.
setSelection(providerId: string, modelId: string): voidProgrammatically set the selection.

Events

EventTypeDescription
aparte-model-changeCustomEvent&lt;AparteModelChangeEventDetail>The provider or the model changed; carries both ids.