@aparte/plugin-model-selector 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/aparte-model-selector.d.ts +55 -0
- package/dist/aparte-model-selector.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +299 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aparté
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @aparte/plugin-model-selector
|
|
2
|
+
|
|
3
|
+
A provider + model selector web component for [aparté](https://github.com/apartejs/aparte). Renders an
|
|
4
|
+
`<aparte-select>` with each registered AI provider grouped into an `<aparte-optgroup>` (a single provider
|
|
5
|
+
renders a flat list), for the BYOK (Bring Your Own Key) pattern.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @aparte/plugin-model-selector @aparte/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import '@aparte/plugin-model-selector'; // registers <aparte-model-selector>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<aparte-model-selector auto-select persist searchable></aparte-model-selector>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`@aparte/core` is the only **peer dependency**. Importing the package registers the
|
|
20
|
+
`<aparte-model-selector>` element as a side effect.
|
|
21
|
+
|
|
22
|
+
| Attribute | Effect |
|
|
23
|
+
|---------------|-----------------------------------------------------------|
|
|
24
|
+
| `auto-select` | Select the first available model on mount |
|
|
25
|
+
| `persist` | Write the selection back to the resolved config |
|
|
26
|
+
| `searchable` | Enable search in the dropdown |
|
|
27
|
+
| `placeholder` | Override the placeholder text (else the active locale) |
|
|
28
|
+
|
|
29
|
+
Fires **`aparte-model-change`** (`{ providerId, modelId, previousProviderId, previousModelId }`) when the
|
|
30
|
+
selection changes. It reads providers from the nearest instance config (`attachConfig`), falling back to
|
|
31
|
+
the global `AparteConfig`.
|
|
32
|
+
|
|
33
|
+
> ESM-only. Part of the aparté monorepo.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aparté Model Selector
|
|
3
|
+
*
|
|
4
|
+
* Web Component for selecting an AI provider + model. Renders an `aparte-select`
|
|
5
|
+
* primitive with providers grouped into `aparte-optgroup`s (a single provider
|
|
6
|
+
* renders a flat list). Enables the BYOK (Bring Your Own Key) pattern.
|
|
7
|
+
*
|
|
8
|
+
* @element aparte-model-selector
|
|
9
|
+
* @attr {boolean} auto-select - Auto-select the first model (default: false)
|
|
10
|
+
* @attr {boolean} persist - Persist the selection through the config
|
|
11
|
+
* @attr {boolean} searchable - Enable search in the dropdown
|
|
12
|
+
* @attr {string} placeholder - Override the placeholder text
|
|
13
|
+
*
|
|
14
|
+
* @fires aparte-model-change - Fired when the selected model changes
|
|
15
|
+
*/
|
|
16
|
+
export declare class AparteModelSelector extends HTMLElement {
|
|
17
|
+
private _currentProviderId;
|
|
18
|
+
private _currentModelId;
|
|
19
|
+
private _providerModels;
|
|
20
|
+
private _aparteSelect;
|
|
21
|
+
private _isLoading;
|
|
22
|
+
private _isRendering;
|
|
23
|
+
private _expandedGroups;
|
|
24
|
+
/** Config governing THIS element — nearest instance boundary, else the global.
|
|
25
|
+
Resolved on connect (multi-instance pages get their own config). */
|
|
26
|
+
private _cfg;
|
|
27
|
+
private _configUnsubscribe;
|
|
28
|
+
private _boundHandleChange;
|
|
29
|
+
private _handleOptgroupToggle;
|
|
30
|
+
static get observedAttributes(): string[];
|
|
31
|
+
connectedCallback(): Promise<void>;
|
|
32
|
+
disconnectedCallback(): void;
|
|
33
|
+
attributeChangedCallback(): void;
|
|
34
|
+
/** Current provider ID. */
|
|
35
|
+
get providerId(): string | null;
|
|
36
|
+
/** Current model ID. */
|
|
37
|
+
get modelId(): string | null;
|
|
38
|
+
/** Programmatically set the selection. */
|
|
39
|
+
setSelection(providerId: string, modelId: string): void;
|
|
40
|
+
private _loadAllProviderModels;
|
|
41
|
+
private _onOptgroupToggle;
|
|
42
|
+
private _render;
|
|
43
|
+
private _renderImpl;
|
|
44
|
+
private _setupEventListeners;
|
|
45
|
+
private _handleChange;
|
|
46
|
+
private _emitChange;
|
|
47
|
+
private _persistSelection;
|
|
48
|
+
private _loadPersistedSelection;
|
|
49
|
+
}
|
|
50
|
+
declare global {
|
|
51
|
+
interface HTMLElementTagNameMap {
|
|
52
|
+
'aparte-model-selector': AparteModelSelector;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=aparte-model-selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aparte-model-selector.d.ts","sourceRoot":"","sources":["../src/aparte-model-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiCH,qBAAa,mBAAoB,SAAQ,WAAW;IAChD,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAA0B;IAEjD;2EACuE;IACvE,OAAO,CAAC,IAAI,CAAmC;IAE/C,OAAO,CAAC,kBAAkB,CAA6B;IAGvD,OAAO,CAAC,kBAAkB,CAAiC;IAC3D,OAAO,CAAC,qBAAqB,CAA2D;IAExF,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCxC,oBAAoB,IAAI,IAAI;IAM5B,wBAAwB,IAAI,IAAI;IAUhC,2BAA2B;IAC3B,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED,wBAAwB;IACxB,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IAED,0CAA0C;IAC1C,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;YAgBzC,sBAAsB;YAwCtB,iBAAiB;IA2C/B,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,WAAW;IA2HnB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,WAAW;IAwBnB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,uBAAuB;CAWlC;AAOD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,uBAAuB,EAAE,mBAAmB,CAAC;KAChD;CACJ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aparte/plugin-model-selector
|
|
3
|
+
*
|
|
4
|
+
* Model + provider selector for aparté chat interfaces. Importing this module
|
|
5
|
+
* registers the `<aparte-model-selector>` custom element as a side effect.
|
|
6
|
+
*/
|
|
7
|
+
export { AparteModelSelector } from './aparte-model-selector.js';
|
|
8
|
+
export type { AparteAIProvider, AparteAIModel, AparteModelConfig, AparteModelChangeEventDetail, } from '@aparte/core';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,YAAY,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,4BAA4B,GAC/B,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { AparteConfig, resolveConfig } from "@aparte/core";
|
|
2
|
+
function esc(value) {
|
|
3
|
+
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4
|
+
}
|
|
5
|
+
class AparteModelSelector extends HTMLElement {
|
|
6
|
+
_currentProviderId = null;
|
|
7
|
+
_currentModelId = null;
|
|
8
|
+
_providerModels = [];
|
|
9
|
+
_aparteSelect = null;
|
|
10
|
+
_isLoading = false;
|
|
11
|
+
_isRendering = false;
|
|
12
|
+
_expandedGroups = /* @__PURE__ */ new Set();
|
|
13
|
+
/** Config governing THIS element — nearest instance boundary, else the global.
|
|
14
|
+
Resolved on connect (multi-instance pages get their own config). */
|
|
15
|
+
_cfg = AparteConfig;
|
|
16
|
+
_configUnsubscribe = null;
|
|
17
|
+
// Bound handlers for cleanup
|
|
18
|
+
_boundHandleChange = this._handleChange.bind(this);
|
|
19
|
+
_handleOptgroupToggle = (e) => {
|
|
20
|
+
void this._onOptgroupToggle(e);
|
|
21
|
+
};
|
|
22
|
+
static get observedAttributes() {
|
|
23
|
+
return ["persist", "auto-select", "searchable", "placeholder"];
|
|
24
|
+
}
|
|
25
|
+
async connectedCallback() {
|
|
26
|
+
this._cfg = resolveConfig(this);
|
|
27
|
+
this._isLoading = false;
|
|
28
|
+
this._providerModels = [];
|
|
29
|
+
this._setupEventListeners();
|
|
30
|
+
const config = this._cfg.getModelConfig();
|
|
31
|
+
if (config.defaultProvider && config.defaultModel) {
|
|
32
|
+
this._currentProviderId = config.defaultProvider;
|
|
33
|
+
this._currentModelId = config.defaultModel;
|
|
34
|
+
} else {
|
|
35
|
+
this._loadPersistedSelection();
|
|
36
|
+
}
|
|
37
|
+
await this._loadAllProviderModels();
|
|
38
|
+
this._render();
|
|
39
|
+
this._configUnsubscribe = this._cfg.subscribe(() => {
|
|
40
|
+
void (async () => {
|
|
41
|
+
const cfg = this._cfg.getModelConfig();
|
|
42
|
+
if (cfg.defaultProvider === this._currentProviderId && cfg.defaultModel === this._currentModelId && this._providerModels.length > 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this._loadPersistedSelection();
|
|
46
|
+
await this._loadAllProviderModels();
|
|
47
|
+
this._render();
|
|
48
|
+
})();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this._aparteSelect?.removeEventListener("aparte-select-change", this._boundHandleChange);
|
|
53
|
+
this.removeEventListener("aparte-optgroup-toggle", this._handleOptgroupToggle);
|
|
54
|
+
this._configUnsubscribe?.();
|
|
55
|
+
}
|
|
56
|
+
attributeChangedCallback() {
|
|
57
|
+
if (this.isConnected && !this._isLoading) {
|
|
58
|
+
this._render();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// Public API
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
64
|
+
/** Current provider ID. */
|
|
65
|
+
get providerId() {
|
|
66
|
+
return this._currentProviderId;
|
|
67
|
+
}
|
|
68
|
+
/** Current model ID. */
|
|
69
|
+
get modelId() {
|
|
70
|
+
return this._currentModelId;
|
|
71
|
+
}
|
|
72
|
+
/** Programmatically set the selection. */
|
|
73
|
+
setSelection(providerId, modelId) {
|
|
74
|
+
this._currentProviderId = providerId;
|
|
75
|
+
this._currentModelId = modelId;
|
|
76
|
+
const compositeValue = `${providerId}::${modelId}`;
|
|
77
|
+
if (this._aparteSelect) {
|
|
78
|
+
this._aparteSelect.value = compositeValue;
|
|
79
|
+
}
|
|
80
|
+
this._emitChange();
|
|
81
|
+
}
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// Data Loading
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
85
|
+
async _loadAllProviderModels() {
|
|
86
|
+
if (this._isLoading) return;
|
|
87
|
+
this._isLoading = true;
|
|
88
|
+
try {
|
|
89
|
+
const providers = this._cfg.getAIProviders();
|
|
90
|
+
const uniqueProviders = Array.from(new Map(providers.map((p) => [p.id, p])).values());
|
|
91
|
+
const config = this._cfg.getModelConfig();
|
|
92
|
+
const tempList = [];
|
|
93
|
+
await Promise.all(uniqueProviders.map(async (provider) => {
|
|
94
|
+
try {
|
|
95
|
+
const models = await this._cfg.refreshProviderModels(provider.id);
|
|
96
|
+
const filters = config.modelFilters?.[provider.id];
|
|
97
|
+
const filteredModels = filters?.length ? models.filter((m) => filters.includes(m.id)) : models;
|
|
98
|
+
if (filteredModels.length > 0) {
|
|
99
|
+
tempList.push({ provider, models: filteredModels });
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn(`[AparteModelSelector] Failed to load models for ${provider.id}:`, error);
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
this._providerModels = tempList;
|
|
106
|
+
} finally {
|
|
107
|
+
this._isLoading = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async _onOptgroupToggle(e) {
|
|
111
|
+
const detail = e.detail;
|
|
112
|
+
if (detail.collapsed) {
|
|
113
|
+
this._expandedGroups.delete(detail.label);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this._expandedGroups.add(detail.label);
|
|
117
|
+
const pm = this._providerModels.find((p) => p.provider.getMetadata().name === detail.label);
|
|
118
|
+
if (!pm || !pm.provider.fetchModels) return;
|
|
119
|
+
const groupEl = e.target;
|
|
120
|
+
if (groupEl && groupEl.tagName === "APARTE-OPTGROUP") {
|
|
121
|
+
groupEl.loading = true;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const freshModels = await this._cfg.refreshProviderModels(pm.provider.id);
|
|
125
|
+
if (freshModels && freshModels.length > 0) {
|
|
126
|
+
pm.models = freshModels;
|
|
127
|
+
this._render(true);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`[AparteModelSelector] Lazy fetch failed for ${pm.provider.id}:`, error);
|
|
131
|
+
} finally {
|
|
132
|
+
const el = e.target;
|
|
133
|
+
if (el && el.tagName === "APARTE-OPTGROUP") {
|
|
134
|
+
el.loading = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// Rendering
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
141
|
+
_render(keepOpen = false) {
|
|
142
|
+
if (this._isRendering) return;
|
|
143
|
+
this._isRendering = true;
|
|
144
|
+
try {
|
|
145
|
+
this._renderImpl(keepOpen);
|
|
146
|
+
} finally {
|
|
147
|
+
this._isRendering = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
_renderImpl(keepOpen) {
|
|
151
|
+
const localePlaceholder = this._cfg.getLocale()["modelSelectorPlaceholder"] || "Select a model...";
|
|
152
|
+
const placeholder = this.getAttribute("placeholder") || localePlaceholder;
|
|
153
|
+
const searchable = this.hasAttribute("searchable");
|
|
154
|
+
const autoSelect = this.hasAttribute("auto-select");
|
|
155
|
+
const wasOpen = keepOpen || this._aparteSelect?.hasAttribute("open");
|
|
156
|
+
let currentValue = this._currentModelId ? `${this._currentProviderId}::${this._currentModelId}` : "";
|
|
157
|
+
if (!currentValue && autoSelect) {
|
|
158
|
+
const first = this._providerModels[0];
|
|
159
|
+
const firstModel = first?.models[0];
|
|
160
|
+
if (first && firstModel) {
|
|
161
|
+
this._currentProviderId = first.provider.id;
|
|
162
|
+
this._currentModelId = firstModel.id;
|
|
163
|
+
currentValue = `${this._currentProviderId}::${this._currentModelId}`;
|
|
164
|
+
setTimeout(() => this._emitChange(), 0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const only = this._providerModels[0];
|
|
168
|
+
const singleProvider = this._providerModels.length === 1;
|
|
169
|
+
const optionsHtml = singleProvider && only ? only.models.map((m) => {
|
|
170
|
+
const key = `${only.provider.id}::${m.id}`;
|
|
171
|
+
return `<aparte-option value="${esc(key)}">${esc(m.name)}</aparte-option>`;
|
|
172
|
+
}).join("") : this._providerModels.map((pm) => {
|
|
173
|
+
const label = pm.provider.getMetadata().name;
|
|
174
|
+
const isCollapsed = !this._expandedGroups.has(label);
|
|
175
|
+
return `
|
|
176
|
+
<aparte-optgroup
|
|
177
|
+
label="${esc(label)}"
|
|
178
|
+
collapsible
|
|
179
|
+
${isCollapsed ? "collapsed" : ""}
|
|
180
|
+
>
|
|
181
|
+
${pm.models.map((m) => {
|
|
182
|
+
const key = `${pm.provider.id}::${m.id}`;
|
|
183
|
+
return `<aparte-option value="${esc(key)}">${esc(m.name)}</aparte-option>`;
|
|
184
|
+
}).join("")}
|
|
185
|
+
</aparte-optgroup>
|
|
186
|
+
`;
|
|
187
|
+
}).join("");
|
|
188
|
+
if (this._aparteSelect) {
|
|
189
|
+
this._aparteSelect.setAttribute("placeholder", placeholder);
|
|
190
|
+
if (currentValue) this._aparteSelect.setAttribute("value", currentValue);
|
|
191
|
+
else this._aparteSelect.removeAttribute("value");
|
|
192
|
+
if (searchable) this._aparteSelect.setAttribute("searchable", "");
|
|
193
|
+
else this._aparteSelect.removeAttribute("searchable");
|
|
194
|
+
if (wasOpen) this._aparteSelect.setAttribute("open", "");
|
|
195
|
+
else this._aparteSelect.removeAttribute("open");
|
|
196
|
+
if (!singleProvider) this._aparteSelect.setAttribute("grouped", "");
|
|
197
|
+
else this._aparteSelect.removeAttribute("grouped");
|
|
198
|
+
const optionsContainer = this._aparteSelect.querySelector(".aparte-select-options");
|
|
199
|
+
if (optionsContainer) {
|
|
200
|
+
optionsContainer.innerHTML = optionsHtml;
|
|
201
|
+
if (currentValue) {
|
|
202
|
+
this._aparteSelect.removeAttribute("value");
|
|
203
|
+
this._aparteSelect.setAttribute("value", currentValue);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const temp = document.createElement("div");
|
|
207
|
+
temp.innerHTML = optionsHtml;
|
|
208
|
+
const newChildren = Array.from(temp.children);
|
|
209
|
+
const childrenToRemove = Array.from(this._aparteSelect.children).filter(
|
|
210
|
+
(c) => c.tagName === "APARTE-OPTION" || c.tagName === "APARTE-OPTGROUP"
|
|
211
|
+
);
|
|
212
|
+
childrenToRemove.forEach((c) => c.remove());
|
|
213
|
+
newChildren.forEach((c) => this._aparteSelect?.appendChild(c));
|
|
214
|
+
}
|
|
215
|
+
if (currentValue) {
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
if (this._aparteSelect) {
|
|
218
|
+
this._aparteSelect.value = currentValue;
|
|
219
|
+
}
|
|
220
|
+
}, 0);
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
this.innerHTML = `
|
|
224
|
+
<aparte-select
|
|
225
|
+
class="aparte-model-selector-select"
|
|
226
|
+
placeholder="${esc(placeholder)}"
|
|
227
|
+
${currentValue ? `value="${esc(currentValue)}"` : ""}
|
|
228
|
+
${searchable ? "searchable" : ""}
|
|
229
|
+
${wasOpen ? "open" : ""}
|
|
230
|
+
${!singleProvider ? "grouped" : ""}
|
|
231
|
+
>
|
|
232
|
+
${optionsHtml}
|
|
233
|
+
</aparte-select>
|
|
234
|
+
`;
|
|
235
|
+
this._aparteSelect = this.querySelector("aparte-select");
|
|
236
|
+
this._setupEventListeners();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
240
|
+
// Event Handling
|
|
241
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
242
|
+
_setupEventListeners() {
|
|
243
|
+
this._aparteSelect?.addEventListener("aparte-select-change", this._boundHandleChange);
|
|
244
|
+
this.addEventListener("aparte-optgroup-toggle", this._handleOptgroupToggle);
|
|
245
|
+
}
|
|
246
|
+
_handleChange(e) {
|
|
247
|
+
const detail = e.detail;
|
|
248
|
+
const previousProviderId = this._currentProviderId;
|
|
249
|
+
const previousModelId = this._currentModelId;
|
|
250
|
+
const [providerId, modelId] = detail.value.split("::");
|
|
251
|
+
this._currentProviderId = providerId || null;
|
|
252
|
+
this._currentModelId = modelId || null;
|
|
253
|
+
this._emitChange(previousProviderId, previousModelId);
|
|
254
|
+
}
|
|
255
|
+
_emitChange(previousProviderId, previousModelId) {
|
|
256
|
+
const detail = {
|
|
257
|
+
providerId: this._currentProviderId || void 0,
|
|
258
|
+
modelId: this._currentModelId || "",
|
|
259
|
+
previousProviderId: previousProviderId || void 0,
|
|
260
|
+
previousModelId: previousModelId || void 0
|
|
261
|
+
};
|
|
262
|
+
this.dispatchEvent(new CustomEvent("aparte-model-change", {
|
|
263
|
+
bubbles: true,
|
|
264
|
+
composed: true,
|
|
265
|
+
detail
|
|
266
|
+
}));
|
|
267
|
+
if (this.hasAttribute("persist")) {
|
|
268
|
+
this._persistSelection();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
272
|
+
// Persistence (opt-in)
|
|
273
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
274
|
+
_persistSelection() {
|
|
275
|
+
if (!this._currentProviderId || !this._currentModelId) return;
|
|
276
|
+
const currentConfig = this._cfg.getModelConfig();
|
|
277
|
+
if (currentConfig.defaultProvider === this._currentProviderId && currentConfig.defaultModel === this._currentModelId) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this._cfg.setModelConfig({
|
|
281
|
+
defaultProvider: this._currentProviderId,
|
|
282
|
+
defaultModel: this._currentModelId
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
_loadPersistedSelection() {
|
|
286
|
+
const config = this._cfg.getModelConfig();
|
|
287
|
+
if (config.defaultProvider && config.defaultModel) {
|
|
288
|
+
this._currentProviderId = config.defaultProvider;
|
|
289
|
+
this._currentModelId = config.defaultModel;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!customElements.get("aparte-model-selector")) {
|
|
294
|
+
customElements.define("aparte-model-selector", AparteModelSelector);
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
AparteModelSelector
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/aparte-model-selector.ts"],"sourcesContent":["/**\n * Aparté Model Selector\n *\n * Web Component for selecting an AI provider + model. Renders an `aparte-select`\n * primitive with providers grouped into `aparte-optgroup`s (a single provider\n * renders a flat list). Enables the BYOK (Bring Your Own Key) pattern.\n *\n * @element aparte-model-selector\n * @attr {boolean} auto-select - Auto-select the first model (default: false)\n * @attr {boolean} persist - Persist the selection through the config\n * @attr {boolean} searchable - Enable search in the dropdown\n * @attr {string} placeholder - Override the placeholder text\n *\n * @fires aparte-model-change - Fired when the selected model changes\n */\n\nimport {\n AparteConfig,\n resolveConfig,\n type AparteConfigClass,\n AparteSelect,\n type AparteOptgroup,\n type AparteAIProvider,\n type AparteAIModel,\n type AparteModelChangeEventDetail,\n type AparteSelectChangeDetail,\n} from '@aparte/core';\n\ninterface ProviderModels {\n provider: AparteAIProvider;\n models: AparteAIModel[];\n}\n\n/**\n * Escape a value before it is interpolated into an HTML string (text node or\n * double-quoted attribute). Model names/ids come from a remote `/models`\n * endpoint and provider labels from consumer config — both hostile-by-default,\n * and both flow through `innerHTML` when the option list is (re)built.\n */\nfunction esc(value: unknown): string {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport class AparteModelSelector extends HTMLElement {\n private _currentProviderId: string | null = null;\n private _currentModelId: string | null = null;\n private _providerModels: ProviderModels[] = [];\n private _aparteSelect: AparteSelect | null = null;\n private _isLoading = false;\n private _isRendering = false;\n private _expandedGroups: Set<string> = new Set();\n\n /** Config governing THIS element — nearest instance boundary, else the global.\n Resolved on connect (multi-instance pages get their own config). */\n private _cfg: AparteConfigClass = AparteConfig;\n\n private _configUnsubscribe: (() => void) | null = null;\n\n // Bound handlers for cleanup\n private _boundHandleChange = this._handleChange.bind(this);\n private _handleOptgroupToggle = (e: Event): void => { void this._onOptgroupToggle(e); };\n\n static get observedAttributes(): string[] {\n return ['persist', 'auto-select', 'searchable', 'placeholder'];\n }\n\n async connectedCallback(): Promise<void> {\n this._cfg = resolveConfig(this);\n // Reset loading flag so a fresh mount always does a full load\n // (prevents a stale _isLoading=true from a previous mount blocking this one)\n this._isLoading = false;\n this._providerModels = [];\n\n this._setupEventListeners();\n\n // Seed selection from the resolved config first (handles race conditions)\n const config = this._cfg.getModelConfig();\n if (config.defaultProvider && config.defaultModel) {\n this._currentProviderId = config.defaultProvider;\n this._currentModelId = config.defaultModel;\n } else {\n this._loadPersistedSelection();\n }\n\n await this._loadAllProviderModels();\n this._render();\n\n // Listen for configuration changes (e.g. from an onboarding flow)\n this._configUnsubscribe = this._cfg.subscribe(() => {\n void (async () => {\n const cfg = this._cfg.getModelConfig();\n\n // Guard: only react if the model config actually changed relative to our state\n if (cfg.defaultProvider === this._currentProviderId &&\n cfg.defaultModel === this._currentModelId &&\n this._providerModels.length > 0) {\n return;\n }\n\n this._loadPersistedSelection();\n await this._loadAllProviderModels();\n this._render();\n })();\n });\n }\n\n disconnectedCallback(): void {\n this._aparteSelect?.removeEventListener('aparte-select-change', this._boundHandleChange);\n this.removeEventListener('aparte-optgroup-toggle', this._handleOptgroupToggle);\n this._configUnsubscribe?.();\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected && !this._isLoading) {\n this._render();\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n /** Current provider ID. */\n get providerId(): string | null {\n return this._currentProviderId;\n }\n\n /** Current model ID. */\n get modelId(): string | null {\n return this._currentModelId;\n }\n\n /** Programmatically set the selection. */\n setSelection(providerId: string, modelId: string): void {\n this._currentProviderId = providerId;\n this._currentModelId = modelId;\n\n const compositeValue = `${providerId}::${modelId}`;\n if (this._aparteSelect) {\n this._aparteSelect.value = compositeValue;\n }\n\n this._emitChange();\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Data Loading\n // ─────────────────────────────────────────────────────────────────────────\n\n private async _loadAllProviderModels(): Promise<void> {\n if (this._isLoading) return;\n this._isLoading = true;\n\n try {\n const providers = this._cfg.getAIProviders();\n\n // Deduplicate providers by ID just in case\n const uniqueProviders = Array.from(new Map(providers.map(p => [p.id, p])).values());\n\n const config = this._cfg.getModelConfig();\n\n // Store in a temporary list to avoid mid-render state corruption\n const tempList: ProviderModels[] = [];\n\n // Fetch all providers in parallel\n await Promise.all(uniqueProviders.map(async (provider) => {\n try {\n const models = await this._cfg.refreshProviderModels(provider.id);\n\n // Apply model filters if configured\n const filters = config.modelFilters?.[provider.id];\n const filteredModels = filters?.length\n ? models.filter(m => filters.includes(m.id))\n : models;\n\n if (filteredModels.length > 0) {\n tempList.push({ provider, models: filteredModels });\n }\n } catch (error) {\n console.warn(`[AparteModelSelector] Failed to load models for ${provider.id}:`, error);\n }\n }));\n\n this._providerModels = tempList;\n } finally {\n this._isLoading = false;\n }\n }\n\n private async _onOptgroupToggle(e: Event): Promise<void> {\n const detail = (e as CustomEvent).detail;\n\n // Track expansion state\n if (detail.collapsed) {\n this._expandedGroups.delete(detail.label);\n return;\n }\n\n this._expandedGroups.add(detail.label);\n\n // Find which provider this optgroup belongs to\n const pm = this._providerModels.find(p => p.provider.getMetadata().name === detail.label);\n if (!pm || !pm.provider.fetchModels) return;\n\n // Show a loading state on the optgroup while we lazily fetch its models\n const groupEl = e.target as HTMLElement;\n if (groupEl && groupEl.tagName === 'APARTE-OPTGROUP') {\n (groupEl as AparteOptgroup).loading = true;\n }\n\n try {\n // Retrieve latest models via Config (provider-agnostic)\n const freshModels = await this._cfg.refreshProviderModels(pm.provider.id);\n\n if (freshModels && freshModels.length > 0) {\n pm.models = freshModels;\n this._render(true);\n }\n } catch (error) {\n console.error(`[AparteModelSelector] Lazy fetch failed for ${pm.provider.id}:`, error);\n } finally {\n const el = e.target as HTMLElement;\n if (el && el.tagName === 'APARTE-OPTGROUP') {\n (el as AparteOptgroup).loading = false;\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n private _render(keepOpen = false): void {\n if (this._isRendering) return;\n this._isRendering = true;\n\n try {\n this._renderImpl(keepOpen);\n } finally {\n this._isRendering = false;\n }\n }\n\n private _renderImpl(keepOpen: boolean): void {\n // Attribute overrides locale\n const localePlaceholder = this._cfg.getLocale()['modelSelectorPlaceholder'] || 'Select a model...';\n const placeholder = this.getAttribute('placeholder') || localePlaceholder;\n const searchable = this.hasAttribute('searchable');\n const autoSelect = this.hasAttribute('auto-select');\n\n // Capture current open state of aparte-select if it exists\n const wasOpen = keepOpen || this._aparteSelect?.hasAttribute('open');\n\n // Determine current value\n let currentValue = this._currentModelId ? `${this._currentProviderId}::${this._currentModelId}` : '';\n\n if (!currentValue && autoSelect) {\n const first = this._providerModels[0];\n const firstModel = first?.models[0];\n if (first && firstModel) {\n this._currentProviderId = first.provider.id;\n this._currentModelId = firstModel.id;\n currentValue = `${this._currentProviderId}::${this._currentModelId}`;\n // Emit the initial change once the element settles\n setTimeout(() => this._emitChange(), 0);\n }\n }\n\n // Build options HTML — single provider: flat list; multiple: grouped optgroups\n const only = this._providerModels[0];\n const singleProvider = this._providerModels.length === 1;\n const optionsHtml = singleProvider && only\n ? only.models.map(m => {\n const key = `${only.provider.id}::${m.id}`;\n return `<aparte-option value=\"${esc(key)}\">${esc(m.name)}</aparte-option>`;\n }).join('')\n : this._providerModels.map(pm => {\n const label = pm.provider.getMetadata().name;\n const isCollapsed = !this._expandedGroups.has(label);\n return `\n <aparte-optgroup\n label=\"${esc(label)}\"\n collapsible\n ${isCollapsed ? 'collapsed' : ''}\n >\n ${pm.models.map(m => {\n const key = `${pm.provider.id}::${m.id}`;\n return `<aparte-option value=\"${esc(key)}\">${esc(m.name)}</aparte-option>`;\n }).join('')}\n </aparte-optgroup>\n `;\n }).join('');\n\n if (this._aparteSelect) {\n // Non-destructive update of attributes\n this._aparteSelect.setAttribute('placeholder', placeholder);\n if (currentValue) this._aparteSelect.setAttribute('value', currentValue);\n else this._aparteSelect.removeAttribute('value');\n\n if (searchable) this._aparteSelect.setAttribute('searchable', '');\n else this._aparteSelect.removeAttribute('searchable');\n\n if (wasOpen) this._aparteSelect.setAttribute('open', '');\n else this._aparteSelect.removeAttribute('open');\n\n if (!singleProvider) this._aparteSelect.setAttribute('grouped', '');\n else this._aparteSelect.removeAttribute('grouped');\n\n // Update children cleanly: prefer direct container update if available\n const optionsContainer = this._aparteSelect.querySelector('.aparte-select-options');\n if (optionsContainer) {\n // Direct update (faster, skips the observer)\n optionsContainer.innerHTML = optionsHtml;\n\n // Force label re-evaluation: remove then re-set value so aparte-select\n // re-scans the newly injected options (setAttribute guard blocks same-value updates)\n if (currentValue) {\n this._aparteSelect.removeAttribute('value');\n this._aparteSelect.setAttribute('value', currentValue);\n }\n } else {\n // Fallback: self-healing mode\n const temp = document.createElement('div');\n temp.innerHTML = optionsHtml;\n const newChildren = Array.from(temp.children);\n\n const childrenToRemove = Array.from(this._aparteSelect.children).filter(c =>\n c.tagName === 'APARTE-OPTION' || c.tagName === 'APARTE-OPTGROUP'\n );\n childrenToRemove.forEach(c => c.remove());\n\n newChildren.forEach(c => this._aparteSelect?.appendChild(c));\n }\n\n // Critical: re-apply value after a microtask so AparteSelect has processed\n // the new options and can resolve the label.\n if (currentValue) {\n setTimeout(() => {\n if (this._aparteSelect) {\n this._aparteSelect.value = currentValue;\n }\n }, 0);\n }\n } else {\n // Full initial render\n this.innerHTML = `\n <aparte-select\n class=\"aparte-model-selector-select\"\n placeholder=\"${esc(placeholder)}\"\n ${currentValue ? `value=\"${esc(currentValue)}\"` : ''}\n ${searchable ? 'searchable' : ''}\n ${wasOpen ? 'open' : ''}\n ${!singleProvider ? 'grouped' : ''}\n >\n ${optionsHtml}\n </aparte-select>\n `;\n this._aparteSelect = this.querySelector('aparte-select');\n this._setupEventListeners();\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Event Handling\n // ─────────────────────────────────────────────────────────────────────────\n\n private _setupEventListeners(): void {\n this._aparteSelect?.addEventListener('aparte-select-change', this._boundHandleChange);\n this.addEventListener('aparte-optgroup-toggle', this._handleOptgroupToggle);\n }\n\n private _handleChange(e: Event): void {\n const detail = (e as CustomEvent<AparteSelectChangeDetail>).detail;\n const previousProviderId = this._currentProviderId;\n const previousModelId = this._currentModelId;\n\n // Parse composite value \"providerId::modelId\"\n const [providerId, modelId] = detail.value.split('::');\n\n this._currentProviderId = providerId || null;\n this._currentModelId = modelId || null;\n\n this._emitChange(previousProviderId, previousModelId);\n }\n\n private _emitChange(previousProviderId?: string | null, previousModelId?: string | null): void {\n const detail: AparteModelChangeEventDetail = {\n providerId: this._currentProviderId || undefined,\n modelId: this._currentModelId || '',\n previousProviderId: previousProviderId || undefined,\n previousModelId: previousModelId || undefined,\n };\n\n this.dispatchEvent(new CustomEvent<AparteModelChangeEventDetail>('aparte-model-change', {\n bubbles: true,\n composed: true,\n detail,\n }));\n\n // Persist if enabled\n if (this.hasAttribute('persist')) {\n this._persistSelection();\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Persistence (opt-in)\n // ─────────────────────────────────────────────────────────────────────────\n\n private _persistSelection(): void {\n if (!this._currentProviderId || !this._currentModelId) return;\n\n // Sync with the config — auto-save is handled by its setModelConfig()\n // if a AparteModelPreferenceProvider has been registered by the host app.\n const currentConfig = this._cfg.getModelConfig();\n if (currentConfig.defaultProvider === this._currentProviderId &&\n currentConfig.defaultModel === this._currentModelId) {\n return;\n }\n\n this._cfg.setModelConfig({\n defaultProvider: this._currentProviderId,\n defaultModel: this._currentModelId,\n });\n }\n\n private _loadPersistedSelection(): void {\n // The resolved config is the single source of truth. The host app is\n // responsible for restoring preferences into it (via restoreModelPreference())\n // before this component mounts.\n const config = this._cfg.getModelConfig();\n if (config.defaultProvider && config.defaultModel) {\n this._currentProviderId = config.defaultProvider;\n this._currentModelId = config.defaultModel;\n }\n // else: no-op — the config is empty\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-model-selector')) {\n customElements.define('aparte-model-selector', AparteModelSelector);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'aparte-model-selector': AparteModelSelector;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aparte/plugin-model-selector",
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
4
|
+
"description": "Provider + model selector web component (<aparte-model-selector>) for aparté — grouped dropdown, BYOK.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": true,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"@aparte-workspace/source": "./src/index.ts",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@aparte/core": "0.2.0-alpha.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.4.0",
|
|
31
|
+
"vite": "^6.0.0",
|
|
32
|
+
"vite-plugin-dts": "^4.5.4",
|
|
33
|
+
"@aparte/core": "0.2.0-alpha.0"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"aparte",
|
|
37
|
+
"plugin",
|
|
38
|
+
"model-selector",
|
|
39
|
+
"web-component",
|
|
40
|
+
"byok"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/apartejs/aparte.git",
|
|
46
|
+
"directory": "packages/plugins/model-selector"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/apartejs/aparte/issues"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"dev": "vite",
|
|
53
|
+
"build": "vite build && tsc -b --emitDeclarationOnly --force",
|
|
54
|
+
"preview": "vite preview",
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"test:run": "vitest run",
|
|
57
|
+
"test:coverage": "vitest run --coverage"
|
|
58
|
+
}
|
|
59
|
+
}
|